[MLton] interrupted system call

Stephen Weeks MLton@mlton.org
Wed, 24 Mar 2004 20:14:28 -0800


> What I was worried about was if installing a singal handler for _any_
> signal meant installing the C side signal handler for all signals.  I.e.,
> we'd wait until we got back to ML to decide whether it is a signal handled
> by the user.

Ah.  This doesn't happen.  That is, we maintain the invariant that C
handler is installed for a signal iff ML will handle the signal.

> Well, I just want to know what to expect from doing
>   fun atomicPrint s = (atomicBegin (); print s; atomicEnd ());
> which is going to incur (multiple) system calls down below.

Good question.  I think the right thing to expect is that the behavior
is the same whether or not any signals arrive during the critical
section.  That could be achieved by 

1. Making atomicBegin block all signals and atomicEnd unblock all
   signals.
   (the observation that this lets us get rid of canHandle seems correct)

OR

2. a. Preventing the ML signal handler from running in the critical section
   AND 
   b. Restarting any system calls that are interrupted by a signal.

We currently do 2a using canHandle.  So, we can either add 2b or
switch to 1.  The problem with 1 is that it is slow, making a system
call for both atomicBegin and atomicEnd. 

Adding 2b still leaves us with your original problem about a system
call not making progress due to being interrupted too much.
Nontermination is another aspect of behavior that we should try to
preserve.  That argues for adding

2. c. Don't let signals prevent a system call from completing.  This
      is done by blocking (some or all) signals when restarting.

> I think it's just two sides of the same coin:
> A) Do I go out of my way to make system calls outside of critical regions
>    so that my signal handler always gets a chance to run.
> B) Do I go out of my way to handle intr exceptions so that I can put
>    system calls inside critical regions.

Maybe I'm still confused, but I don't see why you have to take either
of these choices with 2abc (and maybe you weren't even saying that you
do :-).  Because of 2b, system calls never raise intr.  Because of 2c,
if you make a system call inside a critical region, you are guaranteed
it will get a chace to complete.  As to A, if you make a system call
outside of a critical region, the signal handler will get a chance to
run on the restart loop.  Note that 2c blocks signals; it does not
enter a critical section, so the signal handler can still run.

> I'm just thinking about what it would take to make the basis library
> thread safe.  If we wrap Posix.IO.readVec under a restart loop, then
> either
> 1) we must always call Posix.IO.readVec outside a critical region;
>    seems like it would be hard to make StreamIO and ImperativeIO
>    thread safe.
> 2) we allow calls to Posix.IO.readVec inside a critical region of the
>    Basis;  so, the programmer can assume that thier USR1 signal handler
>    will run if they call Posix.IO.readVec outside a critical region, but
>    not if they call TextIO.inputLine.

(2) seems fine to me.  We use critical sections to make the basis
library thread safe.  It seems OK to expect the programmer to know
that, and hence to not expect their signal handler to be able to run.

I think this problem also goes away once we get CML in.  But I guess
some people might want threads and not CML, so it's good that we think
about it.

> > > re too expensive, my interpretation of Matthews thought was that we
> > > alway run with all signals blocked (more on that below), but when
> > > the current code checks to see if a signal has occured, instead we
> > > unblock the signals and then immediately reblock them and then check
> > > the flag.
> >
> > My interpretation was that the MLton C signal handler, GC_handler,
> > would block all signals (I assume this is OK in a C signal handler, is
> > it?).  Then, GC_finishHandler would unblock them, as it does now.
> > This would prevent a system call from being interrupted by a signal
> > more than once.
> 
> I meant the latter.  But, I don't think you should block all signals,
> just the one you received.

OK.  I think either way works.  Blocking only one signal means that
the restart loop really needs to be a loop, because it can go around
once for each signal.  Blocking all signals means that we only need to
restart the system call once.  I don't see much reason to prefer one
to the other.

> > Here's another option: block the signals if the the system call is
> > interrupted, so that the restart can't be interrupted.
> 
> I don't see why this would be any more efficient than blocking the signal
> in the GC_handler and unblocking them at GC_finishHandler.  (Other than
> the fact that this would leave signals blocked until the system call
> completed, even if the ML signal handler got a chance to run.)

I agree that these approaches are similar.  In addition to the
difference you see, there is also

* One is done in ML, one is done in C.

* Using GC_handler, you know the signal that came in, so you can block
  just that one.  From the ML, you don't know, so you must block all
  signals. 

I don't see too much difference there either.


It feels like we're migrating toward a common understanding of the
solution.  I guess I am becoming (slowly :-) convinced of your
original proposal

  Another option might be to have the C signal handler block signals
  until the ML signal handler gets a chance to run.