
Some thinking out loud about signals
------------------------------------

Problem with signals: you never know when they're delivered.  They are
completely asynchronous, so delivery may happen before the next system
call is made.

This means that a setup where you test for a signal flag that's
incremented by a handler, just before going into select(), has a nasty
race: the handler may have been called and the flag incremented just
after the test, but before the select.

If that happens, select() won't be interrupted, because the signal has
of course already been delivered as far as the kernel is concerned. And
for sure, doing a test closer to the select doesn't solve the race
condition.

Using blocking masks is no solution - although signal delivery is
deferred till the unmasking call, it can still happen after the
unmasking and handling, but before the select().

As far as I can see, there are two solutions to this general problem:

* A technique I used earlier: make the sighandler write to a pipe that's
  included in the select set. This is OK as long as you make sure the
  pipe is nonblocking (otherwise the sighandler can block if called
  often enough before the pipe is emptied). That has the implication
  however that one or more sighandler invocations may go undetected by
  the main program.

  This doesn't have to be much of a problem, as long as the condition
  doesn't last too long and the exact number of handler invocations
  doesn't matter. In most cases it won't, eg. when a sigchld is
  delivered, any number of childs may actually have exited, so you
  already have to loop through wait4(WNOHANG) anyway until no more
  dead childs are found.

* don't use sighandlers at all; set all non-IGN/DFL handlers to an empty
  handler that does nothing but reinstall the handler, and keep their
  delivery blocked using sigprocmask. At the point in the main program
  where you want to test for delivered signals, you use use sigpending 
  to find out which were.

  By briefly unblocking and re-blocking delivery, you effectively clear
  all pending signals (whether they had been handled or not).

  This does create a race that can cause an event to be missed, between
  the sigpending() test and the re-blocking of delivery.

* do all the work within the signal handler itself, and protect the
  section in the main program in which this work may not be carried out,
  using a sigprocmask.

  This is possible, but I'd rather do the opposite: signals are delived
  nowhere but at the exact location in the main program where I want to
  handle them. 

It seems that there is a tradeoff: if you want to control signal
delivery so that you can handle them in a particular place in your main
program, there is the possibility to miss a signal or delay it for a
long time, which isn't very desireable. (first two options)

The other option is the last one, but as it limits control and requires
the work associated with signals to happen in a separate function, it's
isn't too desireable either.

In short, the requirements for a solution are as follows:

a. it must be possible to interrupt blocking syscalls and do what's
   needed. Handling signalled events should not be deferred until some
   other event happens that unblocks the syscall.

   This implies that the signal must either be unmasked during those
   syscalls, or that the pipe-solution is used - although that only
   works when select is the only blocking syscall.

b. the exact number of invocations of a handler that happened between
   the last test and the next one isn't important, but it must always be
   possible to distinguish between zero and more calls.

   It must be possible to test and clear this difference atomically at a
   particular point in the main program.

   This implies that any masking/unmasking trick won't work: there's 
   always the period after the test, before the masking that effectively
   queues the signal in the pending set.

It seems that the only real solution is to indeed have the handler
itself add the event of the call to a queue, or flag it in a variable.

Originally, I thought that using a pipe as the queue I could also solve
the interrupting problem by including the reading end in the select.
However, on the writing end, pipes will either block or discard data
when they're full. It's much better to use a queue that doesn't take
any memory, such as a simple counter.

But in that case, although delivery and queueing of a signal *during*
the select will interrupt it, delivery outside the select will not cause
it to exit immediately. And as said, testing the counter before doing
the select doesn't help - it only creates a race.

A possibility may be a hybrid, where the signal handler both increments
a counter and writes a byte to a pipe.

The main program uses the pipe to cause queued and unhandled signals to
interrupt the select, and then uses a logical "or" on the counter and
the pipe to detect the eventm, and an interlocking reset mechanism to
avoid races.

Let's see. When select says 'ready for reading', you handle the event
and you reset the counter before emptying the rest of the pipe.

Then, if a signal arrives after resetting the counter but before the
pipe is emptied, the pipe won't have the signal but the counter will. If
it arrives even later, both will have it.

Before the select, you test the counter, handle the event and you empty
the pipe before resetting the counter.

Then, if a signal arrives before emptying the pipe, the pipe won't have
it but the counter will. Hm. That doesn't do select much good; it will
just block and defer detection until it's done. Damn.

...

No, the pipe must always have something in it as long as the event
hasn't been completely handled and cleared yet. So it must be like this:

The signal handler empties the pipe first and then writes a byte to it.
To all parties concerned, this appears to happen atomically; a signal
handler won't interrupt itself and surely the main program can never
interrupt a signal handler ;-).

That means that the firstmost byte in the pipe is the actual 'signalled'
flag. This can never *not* be set when the handler was actually called.

As long as this flag is set, select() will always exit immediately if
you include the reading end of the pipe. And, testing and resetting
'atomically' (as far as the handler is concerned) can be done by reading
the single byte from it in non-blocking mode.

If the read returns no data, there's nothing to be done. If read returns
something else, we just tested and cleared the flag (which is the
existence of the data byte in the pipe) atomically.

The atomicity comes from the fact that if a reading end of a pipe has
two readers (effectively, the signal handler that empties it and puts it
back, and the main program), a byte present in it has to go either to
one or the other, but it can never disappear, or go to both.

Eureka.


Now we want to refine the technique a bit, as we want to know which
signals are in the set. The idea is this:

Instead of a byte, we use a 32-bit or even sigset_t word that holds the
'pending since last test' set in the pipe.

The signal handler, when called, 'empties' the pipe by reading the word
(using zero if the pipe didn't have any data), OR'ing the received
signal into it and writing it back.

The main program needs to ensure that the read in the signal handler
doesn't interfere with the read that it uses to test-and-clear the set.
This used to be easy because of the single byte, but if two processes
each attempt to read four bytes from a pipe, you can't guarantee that
one doesn't gets a short read of two, causing the other two bytes to go
to the other.

But if the main process temporarily blocks signal delivery using
sigprogmask around the nonblocking read, we're done!

