Implementation of "safe signals" in Net::FTPServer
--------------------------------------------------
Richard Jones, 31st July 2001.

1. Background - the Problem
---------------------------

Perl 5.7 implements a new feature, so called "safe signals". About
time too! In previous versions of Perl, signals are anything but
safe. Suppose you have a typical signal handler like this from the
Perl manual pages:

      $SIG{CHLD} = sub { wait };

This handler is wrong in a number of respects, not least because it
doesn't call "wait" often enough. But most importantly, this signal
handler could be called at any time. There are two problems with
this:

* Lots of C library calls aren't reentrant. If Perl happens to be
  in the middle of a "malloc" (for example) when the signal handler
  is invoked, then you'll get a core dump, if you're lucky, or
  memory corruption, if you're not.

* Perl itself isn't reentrant. If the Perl interpreter is in the
  middle of executing something when the signal handler is called,
  then you'll get similar problems. Generally the result will be
  a core dump.

This isn't much of a problem in typical, low load programs. Chances
are your typical program spends most of its time waiting on system
calls, and it's quite safe to receive a signal during a system call,
because neither the C library, nor the Perl interpreter will be
running.

However, in Net::FTPServer under load, it is a *big* problem.

2. Perl 5.7's safe signals
--------------------------

In Perl >= 5.7, signals are not executed asynchronously. Instead,
signals are queued in the Perl interpreter and are executed at
safe "sequence points" (roughly corresponding to a ";" semicolon
in the code) between statements. This ensures that signal handlers
can run safely without the reentrancy problems outlined above.

3. Net::FTPServer safe signals
------------------------------

Net::FTPServer is not in the position where we can force people to
upgrade to Perl 5.7. We want to support Perl 5.00503 for the foreseeable
future.

The only way to provide safe signals in Net::FTPServer was to include
a tiny piece of C code which enqueues the asynchronous signals and
then allows Perl to test for signals at a safe, synchronous point in
the code.

The C code FTPServer.xs actually handles the three problematical
signals (SIGCHLD, SIGHUP and SIGURG). This code is straightforward.

The Perl function _check_signals needs to be called periodically. Its
job is to check for any outstanding signals and run their handlers
synchronously. This function is called in just four places:

* In the main "accept" loop of the parent process (see below).
* In the command loop of the child process, just after a command has
  been read.
* In the store loop while files are being uploaded, to check for SIGURG.
* In the retrieve loop while files are being downloaded, to check for
  SIGURG.

The only difficult case is the "accept" loop. Normally, Net::FTPServer
would do something like this in the parent:

  for (;;)
  {
    my $sock = $control_socket->accept;

    # Fork off a child process to handle new "$sock" connection.
  }

If we simply add a call to _check_signals then we get:

  for (;;)
  {
    my $sock = $control_socket->accept;

    $self->_check_signals;

    # Fork off a child process to handle new "$sock" connection.
  }

However, this doesn't work very well. A particular problem is that
when a signal is received, it doesn't actually interrupt the "accept"
call. Hence the only time that signals are ever processed is when
a new connection is accepted. This could cause zombie children to
be left hanging around for much longer than acceptable, and it's
also a real problem when you send SIGHUP to restart the process.
There could be a long delay after sending the SIGHUP before the
process actually restarts.

So this code instead uses select(2) to check every few seconds for
a signal. This works well and doesn't cause any significant load on
the processor.

4. Safe signals - the Future
----------------------------

We will continue to use this implementation of safe signals until:

* Someone comes up with a better way to do it, or:
* Everyone has migrated to Perl 5.8, and earlier versions are
  considered obsolete.
