Boost logo

Boost :

From: Angus Leeming (angus.leeming_at_[hidden])
Date: 2004-06-23 09:16:25


John Maddock wrote:

>> > Surely there's no need to call wait until the parent asks for the
>> > return value: In fact I'd kind of like the library to be similar
>> > to Boost.Threads - a child process is an object that can be
>> > waited upon, the library would only need to do something "fancy"
>> > like installing a signal hander, if the child object's destructor
>> > is called, without the object ever being waited upon.
>>
>> Consider this:
>>
>> int main() {
>> child::process mozilla;
>> mozilla.spawn("mozilla");
>> return 0;
>> };
>>
>> You're suggestion, that child::process::~process() invokes
>> wait() means that this program will not exit until mozilla exits.
>> That doesn't seem reasonable here. Even less so if the function
>> launching mozilla was in a control loop...
>
> I didn't say that (well I hope I didn't), I'm saying that if
> there has been no explicit wait then the destructor has to
> take care of cleanup (as far as is possible in such a case),
> it should *not* wait as you rightly point out.

Ok, John, I now see what you're talking about. Apologies for getting
the wrong end of the stick first time around.

See below for my interpretation of how your suggestion would look in
terms of code. Here's my take on it:

1. If I don't invoke is_running() or exit_status() then any zombie is
left until the destructor is called. That seems to be bad, period.

2. The resulting code seems overly complex. If I install a signal
handler then is_running() and exit_status() become a trivial lookup
of the std::map<pid_t, int> that stores the exit status of reaped
children.

3. What happens if a signal handler has been installed? Eg, by the
destructor of a previous instance of the class. Should the signal
handler reap only specific pids? Otherwise, the calls to waitpid
below are going to fail because the zombie will have been reaped
already.

Can you explain why you favour doing things your way? (Assuming that
I've interpreted "your way" correctly this time ;-)

Regards,
Angus

/** @class unix_instance controls the interaction of the parent
 * with a child process.
 *
 * An instance of unix_instance maps to a single child process.
 * It cannot be copied because the underlying child process cannot
 * be duplicated.
 *
 * This child process is launched through the @c spawn() member
 * function which can be invoked successfully only once.
 * Therafter, an internal flag is set and an attempt to launch
 * a second child will fail.
 */
class unix_instance : noncopyable {
public:
    /**
     * Initializes member data but is otherwise a no-op.
     * The @c spawn() member function must be invoked explicitly
     * to launch a child process.
     */
    unix_instance()
        : ppid_(0),
          exit_status_(-1),
          error_(0),
          has_been_spawned_(false)
    {}

    /**
     * Closes any open file descriptors.
     * Can do this because of the one-to-one semantic between
     * unix_instance and the child process.
     *
     * If (ppid_ != 0), then the child is still running. Install
     * a signal handler to ensure that the zombie is reaped.
     */
    ~unix_instance();

    /**
     * @brief Report whether the process is consuming
     * system resources.
     * @return @c true if the child process has been launched and
     * has not yet exited.
     */
    bool is_running()
    {
        if (ppid_ == 0)
            // The process has not yet been started or
            // has been reaped already.
            return false;

        int status;
        pid_t const pid = ::waitpid(ppid_, &status, WNOHANG);
        if (pid == 0)
            return true;
        if (pid == -1) {
            // Shouldn't happen? The process has been reaped already.
            ppid_ = 0;
            return false;
        }
        // The process has ended and the zombie was reaped by the
        // call to ::waitpid.
        ppid_ = 0;
        exit_status = status;
        return false;
    }

    /**
     * @return The exit status of the child process.
     * If the process has not completed (or, indeed, started),
     * returns -1.
     */
    int exit_status()
    {
        // Code similar to that in is_running(), above.
    }
        
    /**
     * @return The error code of the most recently failed
     * operation, or zero.
     */
    int error() const { return error_; }

    /**
     * @brief Launch the child process.
     * @param data user-specified data defining the child process.
     * @returns true if the process was started successfully.
     *
     * This function can be called successfully only once for any
     * one instance of the class. Thereafter, an internal flag is
     * set and the function will no longer attempt to launch a
     * child process. This occurs because the class has the
     * semantics that one instance of the class maps to a single
     * child process.
     *
     * If an error occurs whilst launching a child process, then
     * the error code will be set to one of the possible errors
     * for @c pipe(), @c fork() etc. See your system's
     * documentation for these error codes.
     *
     * If this happens, then the user <em>can</em> invoke
     * spawn again.
     */
    bool spawn(process_data const & data);

private:
    /// PID of process.
    pid_t ppid_;
    /// Holds the exit status of the child process.
    int exit_status_;
    /// Holds errno if, eg, @c ::pipe() or @c ::fork() fails.
    int error_;
    /**
     * Used internally to flag whether a child process has been
     * spawned successfully. A process can be spawned once only,
     * so once @c spawn() returns @c true, any subsequent invocation
     * will return @c false.
     */
    bool has_been_spawned_;
};


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk