Is it safe to call getProcessExitCode more than once?

What will happen if I call getProcessExitCode for the same process twice? Will that block? Cause an error? Or return the same child's exit code again? I assume the function is (under Unix) based on wait(2), right? In that case, how does the following warning from the manual page translate to Haskell? ERRORS ECHILD if the process specified in pid does not exist or is not a child of the calling process. (This can happen for one's own child if the action for SIGCHLD is set to SIG_IGN. See also the LINUX NOTES section about threads.) The reason I am asking is because I want to avoid zombie processes by doing a "catch-all" getProcessExitCode for every process I spawn through a finalizer. I'd just like to know whether it's safe before I do it. :-) Peter

On 2004-10-26, Peter Simons
What will happen if I call getProcessExitCode for the same process twice? Will that block? Cause an error? Or return the same child's exit code again?
Assuming it is based on wait() or one of its derivatives, and I suspect it is, you cannot call it more than once for a single process. Once you have wait()ed for a process, the data about its termination is no longer tracked by the kernel, so you can no longer retrieve it. A Zomebie process is precisely one that is dead but has not yet been waited upon. They exist so that they can be waited upon. It should be pretty easy to install a SIGCHLD handler that reaps children whenever one dies. Such code is found all over the place in C. (And Python, and Perl, and... well everywhere except Java, probably.) -- John Goerzen Author, Foundations of Python Network Programming http://www.amazon.com/exec/obidos/tg/detail/-/1590593715

John Goerzen writes:
Assuming it is based on wait() or one of its derivatives, and I suspect it is, you cannot call it more than once for a single process.
That's what I _assume_, too, but a definite answer would be nice. In the meanwhile, I have found out that it might not be safe to call it once, even: CaughtException waitForProcess: does not exist (No child processes) That's a child I _did_ start and which apparently terminated before I called waitForProcess. Shouldn't I be getting the exit code of that process rather than an exception? Do waitForProcess and getProcessExitCode differ in their behavior other than that one blocks and other doesn't? Peter

Peter Simons wrote:
John Goerzen writes:
Assuming it is based on wait() or one of its derivatives, and I suspect it is, you cannot call it more than once for a single process.
That's what I _assume_, too, but a definite answer would be nice.
In the meanwhile, I have found out that it might not be safe to call it once, even:
CaughtException waitForProcess: does not exist (No child processes)
That's a child I _did_ start and which apparently terminated before I called waitForProcess. Shouldn't I be getting the exit code of that process rather than an exception?
I can think of two reasons why this might be happening: 1. SIGCHLD is being ignored (SIG_IGN); the Process library doesn't appear to be doing this, but something else might. 2. Something else (e.g. the RTS) is handling SIGCHLD and reaping the process automatically.
Do waitForProcess and getProcessExitCode differ in their behavior other than that one blocks and other doesn't?
Both call waitpid(); getProcessExitCode uses WNOHANG, while
waitForProcess doesn't.
They differ in their handling of errors. waitForProcess will throw an
exception if waitpid() indicates any error (except EINTR, where it
just retries the waitpid() call), whereas getProcessExitCode will
return Nothing. Both will throw an exception if the process terminated
on a signal.
--
Glynn Clements

Glynn Clements writes:
Both [waitForProcess and getProcessExitCode] will throw an exception if the process terminated on a signal.
So if I terminate a process manually, I'll have to wait for the ExitCode to avoid a zombie process, and waiting for the ExitCode invariably throws an exception. Or do I misunderstand something? Peter

On Thu, Oct 28, 2004 at 06:27:42AM +0200, Peter Simons wrote:
Glynn Clements writes:
Both [waitForProcess and getProcessExitCode] will throw an exception if the process terminated on a signal.
So if I terminate a process manually, I'll have to wait for the ExitCode to avoid a zombie process, and waiting for the ExitCode invariably throws an exception.
It's just the way that Unix process management works. I guess you have to catch the exception to handle it well. This is part of the aspect that makes writing shells so complicated. Dave

David Brown wrote:
Both [waitForProcess and getProcessExitCode] will throw an exception if the process terminated on a signal.
So if I terminate a process manually, I'll have to wait for the ExitCode to avoid a zombie process, and waiting for the ExitCode invariably throws an exception.
It's just the way that Unix process management works. I guess you have to catch the exception to handle it well. This is part of the aspect that makes writing shells so complicated.
I think that Peter was referring primarily to the fact that the
Haskell interface to waitpid() throws an exception if the process
terminated due to a signal, not the fact that you have to "reap"
children to prevent the accumulation of zombies.
The C interface is that waitpid() (and similar) return a status code;
you can then use the macros from

Peter Simons wrote:
Both [waitForProcess and getProcessExitCode] will throw an exception if the process terminated on a signal.
So if I terminate a process manually, I'll have to wait for the ExitCode to avoid a zombie process, and waiting for the ExitCode invariably throws an exception.
Or do I misunderstand something?
No, that seems correct.
Although, depending upon the OS, setting SIGCHLD to SIG_IGN may cause
processes to be reaped automatically (i.e. not become zombies), so
that's a possible alternative.
--
Glynn Clements

Glynn Clements writes:
Although, depending upon the OS, setting SIGCHLD to SIG_IGN may cause processes to be reaped automatically (i.e. not become zombies), so that's a possible alternative.
I think I've got it under control now. I'm using this wrapper to make sure there are no unwaited-for child processes: type ExternHandle = MVar (Handle, Handle, Handle, ProcessHandle) -- |Run an external process and store its handle in an -- 'MVar' with a finalizer attached to it that will close -- the handles and kill the process when the MVar falls out -- of scope. extern :: FilePath -> [String] -> IO ExternHandle extern path args = do r <- runInteractiveProcess path args (Just "/") (Just []) mv <- newMVar r addMVarFinalizer mv (catch (cleanup r) (const (return ()))) return mv where cleanup (hin, hout, herr, pid) = do terminateProcess pid >> safeWaitForProcess pid hClose hin >> hClose hout >> hClose herr return () -- |Wait 10 seconds max. If the process hasn't terminated by -- then, throw an exception. If the child process has been -- terminated by a signal, return @ExitFailure 137@. This is -- a kludge. So it will probably be in here forever. safeWaitForProcess :: ProcessHandle -> IO ExitCode safeWaitForProcess pid = timeout maxwait loop >>= maybe badluck return where loop = catch loop' (\_ -> return (ExitFailure 137)) loop' = wait >> getProcessExitCode pid >>= maybe loop' return wait = threadDelay 1000000 -- 1/10 second maxwait = 10000000 -- 10 seconds badluck = fail "timeout while waiting for external process" It's ugly, but it seems to work. Peter
participants (4)
-
David Brown
-
Glynn Clements
-
John Goerzen
-
Peter Simons