John Levon wrote: > Angus, this has *nothing* to do with "second signals". Yes, we need > to take care of that issue, but it's not what I'm talking about. > This is about interrupting *non-signal context* code.
Sorry about the tone of that mail. I think I got out of the wrong side of bed this morning. > Can you resend me your patch? I could, but I won't. I've read a lot more and understand a lot more. In fact, I've been drafting a proposal, so may as well post it. The forkedcall code should: * use a SIGCHLD signal to inform us that a child process has completed. * fill a global store in the handler routine in a guaranteed-safe way. * Finish the clean-up in the main lyx loop. It should uses sigprocmask to block any subsequent SIGCHLD signals from overwriting the global store whilst the clean-up is performed. Details follow below. Please try and rip it to shreds. Angus Design details ============== This signal handler is guaranteed to be safe even though it may not be atomic: int completed_child_status; sig_atomic_t completed_child_pid; extern "C" void child_handler(int) { // Clean up the child process. completed_child_pid = wait(&completed_child_status); } (See the signals tutorial at http://tinyurl.com/3h82w.) It's safe because: 1. wait(2) is guaranteed to be async-safe. 2. child_handler handles only SIGCHLD signals so all subsequent SIGCHLD signals are blocked from entering the handler until the existing signal is processed. This handler performs 'half' of the necessary clean up after a completed child process. It prevents us leaving a stream of zombies behind but does not go on to tell the main LyX program to finish the clean-up by emitting the stored signal. That would most definitely not be safe. The only problem with the above is that the global stores completed_child_status, completed_child_pid may be overwritten before the clean-up is completed in the main loop. However, the code in child_handler can be extended to fill an array of completed processes. Everything remains safe so long as no 'unsafe' functions are called. (See the list of async-safe functions at http://tinyurl.com/3h82w.) struct child_data { pid_t pid; int status; }; // This variable may need to be resized in the main program // as and when a new process is forked. This resizing must be // protected with sigprocmask std::vector<child_data> reaped_children; sig_atomic_t current_child = -1; extern "C" void child_handler(int) { child_data & store = reaped_children[++current_child]; // Clean up the child process. store.pid = wait(&store.status); } That is, we build up a list of completed children in anticipation of the main loop then looping over this list and invoking any associated callbacks etc. The nice thing is that the main loop needs only to check the value of 'current_child': while (!finished) { if (current_child != -1) handleCompletedProcesses(); if (fl_check_forms() == FL_EVENT) { ... } } handleCompletedProcesses now loops over only those child processes that have completed (ie, those stored in reaped_children). It blocks any subsequent SIGCHLD signal whilst it does so: // Used to block SIGCHLD signals. sigset_t newMask, oldMask; ForkedcallsController::ForkedcallsController() { reaped_children.resize(50); signal(SIGCHLD, child_handler); sigemptyset(&oldMask); sigemptyset(&newMask); sigaddset(&newMask, SIGCHLD); } void ForkedcallsController::handleCompletedProcesses() { if (current_child == -1) return; // Block the SIGCHLD signal. sigprocmask(SIG_BLOCK, &newMask, &oldMask); for (int i = 0; i != 1+current_child; ++i) { child_data & store = reaped_children[i]; // Go on to handle the child process ... } // Unblock the SIGCHLD signal and restore the old mask. sigprocmask(SIG_SETMASK, &oldMask, 0); }