paddybyers/node — Issue #18: SIGCHLD handling
Author: paddybyers
Created: 2011-11-14 08:30:24+00:00
State: open

With multiple event loops, SIGCHLD handling - specifically, the ability to be notified of the exit of a process that you have spawned - does not work. Currently node installs a SIGCHLD handler that causes callbacks to run in the default event loop. This reaps processes and notifies any child_watchers registered for the process in question.

The issues we have with this in the (library + multiple loop) case are:

- there is no guarantee that (as a library) we will ever see SIGCHLD. The enclosing app might block it, or might register to handle it itself. This is true in the case of an Android java process.

- event loops other than the default will wish to spawn children and be notified of their exit. These event loops may well outlive the default loop; so far, in fact, there is no need at all to start the default loop.

Before discussing how to address this, it is important first to decide some requirements questions:

- **is it a requirement to be able to get notified of children that were not spawned by child_process.spawn()?** This works now, but fi this is a requirement then (without polling) it is not possible to meet this requirement without using SIGCHLD. I think it should be acceptable to say that a child_watcher will only be guaranteed to deliver events for processes explicitly spawned using child_process.spawn() (or exec(), execFile(), which use spawn() under the hood). If some other native module uses a private means to spawn children, then it is also responsible for doing whatever is necessary, within the constraints of its own target environment, to handle associated events, reaping, etc).

- **is it a requirement to be notified of suspend and resume events, or just exit()?** I think it would be OK if we continue to support exit events only, but I have no visibility of the practical requirement to see suspend and resume.

- **is it a requirement to be able to get events even if the watcher is started after the child is spawned?** This is currently possible, but it does limit the scope for alternative solutions to SIGCHLD, so I think it would be OK to say that the watcher has to be added synchronously as part of starting the child.

- **is it acceptable to insist that the default loop is started, and that this loop outlives all other loops?** I think we should avoid making this a requirement if possible.

- **if the environment of the enclosing app does not give us free access to do what we like with SIGCHLD, is it nonetheless safe to assume that the environment will reap children, even if we don't see the events ourselves?** I think we have to assume this, otherwise we would have to ban spawn() completely, or poll, and both are unacceptable.

So, then what are the possible solutions?

1 Do nothing. If someone wants to spawn child processes, then this must be done from the default loop. Isolates that are fork()ed cannot spawn children. This is a pretty poor option but is achievable.

2 Continue to use SIGCHLD. When operating as a library, just impose a constraint that we have to be able to register a SIGCHLD handler. This is not an option on Android - so it would mean that such environments cannot spawn(). This is not a showstopper necessarily but would then require children to be spawned using Android's own (java) Runtime.exec() family of APIs.

If using SIGCHLD still, there is still the question of how to deliver events to non-default event loops. I think there are two options:

2a handle the event in the default loop - requiring it to outlive all other loops - decide which event loop it should be handled by, and send an async event to that loop; or

2b have a reaper thread that lives forever but is not associated with any event loop. In this thread, reap the child and then send an async event to the to event loop that must handle the lifecycle indications for that child.

I would be inclined to go for 2b but welcome input.

3 Use an alternative means to detect child exit. There might be other solutions, but one possible approach is to create a socketpair at the time the child is spawned, with one end open in the parent, and the other end open in the child. Once the child exits, the parent's end will become readable (with an EOF), and this event can be handled naturally in the event loop that spawned the child.

The limitations of this approach are:

- this will only deliver exit events, not suspend or resume;

- this will not see events for children not spawned explicitly by node;

- it relies on knowing that the environment will still reap the children.

3 seems to be a promising option for the library case at least.