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);
}



Reply via email to