If I have the PID of a process that was killed, how might I get the return status of the process?

I am particularly interested to know if the return status was 143 (kill) or 137 (kill -9) as I don't know what is killing the process. It is a Python script being run from a PHP script on a server that I don't have root access for. The Python script runs for a few minutes, but PHP is limited to running for 30 seconds. PHP has facility to get the return status with exec() however that won't work if the PHP script has timed out!


This is almost certainly determined by (the default) php.ini or equivalent setting:

max_execution_time = 30

TLDR: if you are certain that PHP is really exiting first, and something is killing your script, then you can either use daemon to wrap and monitor the Python process (in non-restarting mode, i.e. without --respawn), or add signal handers to the Python script.

In general, if you are able to run a shell as the userid of the script, then you should be able to strace or truss the process, e.g. on linux you can do this reasonably effectively:

 $ sleep 60 &
 [1] 10873
 $ strace -e trace=signal,process  -p $!
 Process 10873 attached - interrupt to quit
 +++ killed by SIGKILL +++
 Process 10873 detached

The sleep process was terminated with a kill -9 from another terminal. kill -9 would not be common though, since a process would not be able to trap this and cleanly exit.

To monitor with daemon use a variation on:

daemon -i --errlog=/tmp/mypy.log -- /usr/bin/python [...]

This will log any signal related termination. Add --dbglog and --debug=2 if you want to log every exit regardless.

As noted elsewhere if the process has already terminated, it's gone. Only the parent (or init) would have been able to obtain its exit code, and unless it was logged (possibly using process accounting or auditing), the exit code is lost.

Internally for timeout handling on *nix platforms, PHP sets up a SIGALARM or SIGPROF with the specified timeout. That signal handler simply calls the internal zend_error() function. It does however also call any registered callbacks and error handlers, which may be of use to you.

If the default error handler kicks in, the PHP exit code will be, I believe, 255, since a timeout is an E_ERROR.

Also note, the convention of an exit code as 128+N, where N is the signal number, is due to the behaviour of certain shells (including bash) and many APIs. The actual process exit code after a signal is system dependent. It is probably just the signal number, this is generally the case on Linux. The wait() group of system calls provide better details of the process exit. PHP follows the shell convention, e.g. if you are using popen() and pclose() you will see 128+N as the exit code when a signal terminated the process.

Another consideration here is the behaviour of PHP time limits, if you check the set_time_limit() documentation you will see

The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running. This is not true on Windows where the measured time is real.

You can prove this with a short script:

# for (;;);
$exe="sleep 20";
print "exit code: " . pclose(popen($exe, 'r'));

Invoke with time php -d max_execution_time=5 -f sleep3.php. The script will run for 20 seconds, there will be no error. If you kill the sleep process, e.g. kill -USR1 $(pgrep sleep) in another terminal, you will see an exit code of 138 (128+SIGUSR1).

If you uncomment the infinite for(;;) loop, the script will terminate after 5 seconds.

  • The status reported to the parent process (via the wait system call family) encodes the 8-bit value passed to _exit if the process exited normally, the signal number if the process was killed by a signal, and a few more flags. I don't know how PHP reports it, but typically it wouldn't be “just the signal number”, it would be the signal number bit-shifted and with other bits set. – Gilles Aug 23 '13 at 7:26

A process that has been killed no longer exists on the system. In fact, on a busy host, there's a pretty good chance that a long-running and since killed process has had its PID reused by some other process, so you'd end up looking at something totally unrelated!

When a process gets killed, it does not return anything. It simply ceases to exist. This in contrast to a catchable signal such as HUP or USR1, which the process can (but does not necessarily) catch and handle in any way it sees fit (including exiting with a defined exit code).

If you have sufficient access, you might be able to install some sort of library or kernel module which intercepts and records the syscalls which ultimately result in the process in question receiving a signal, but it does seem easier to as pjc50 suggested just add a wrapper script and record the exit status somewhere.

An alternative would be to not make the user wait but rather move to an asynchronous architecture for whatever you are doing that takes so long to complete.

  • “When a process gets killed, it does not return anything.” True but irrelevant. When a process gets killed, the signal number is reported over the same channel that reports the exit status for a process that exits normally. – Gilles Aug 22 '13 at 21:53
  • 1
    @Gilles I'm not sure whether that's irrelevant, actually. (It might be, but not necessarily for the reason you are implying.) Note the phrasing of the question: "If I have the PID of a process that was killed, how might I get the return status of the process?" So the process no longer exists by the time the OP wants its return status, meaning there's nothing left to hook on to or query. The past tense form "was" makes this a very different question than, say, "how can I get the return value of a process that runs longer than the program invoking it?". – a CVn Aug 23 '13 at 7:18
  • 1
    The “return status” of a process can mean three different things: the 8-bit value passed to _exit (only if the process exited normally), the status reported by wait and family (which encodes the value passed to exit if the process exited normally, the signal number if the process was killed by a signal, and some more flags), or the condensed 8-bit status reported by shells in $? where signals are converted to values above 128. By the numbers in the question, the asker was talking about $?, but needs to be educated about how it's made. – Gilles Aug 23 '13 at 7:23

You want waitpid(), but I don't know if that's available in PHP. Don't forget to add WNOHANG if you don't want it to block. However, that only works if the PID is of a child of the calling process.

It might be easiest to put a wrapper script around the python script that records its termination status in a log file somewhere.


Getting the exit status if PHP is no longer the parent process is simple. Wrap the Python script in a BASH script to record the exit status in a file. For example:

false # false is a program that always has an exit status of 1.
echo $? > exit_status.log

Will put '1' into exit_status.log. Just replace false with your actual Python script.

Triggering the script to run as disowned non-blocking process in the background from PHP is more difficult. Consider triggering the process with a Cron schedule and avoid PHP all-together!

Otherwise, a possible solution is to call the script using at. Here is a complete solution:

From PHP call:

exec("at -f commands now");

In the file, commands:

sleep 10 # Do nothing for 10 seconds
false # Return status 1
echo $? > exit_status.log

If you do not have root access and at is not installed, then compile it locally into your home directory, by using a something like ./configure --prefix=$HOME/local. Otherwise, ask the administrator to install the package.

Further reading: man at, man cron

  • Wouldn't atd still need to be running outside of the web server's process and security context, though? – a CVn Aug 22 '13 at 10:48
  • Thank you. It was difficult to pick an 'accepted' answer here! – dotancohen Aug 25 '13 at 12:39

Another alternative:

From PHP:


In wrapper.sh:

./wrapperlogger.sh >/dev/null 2>&1 &

In wrapperlogger.sh:

echo $? > exit_status.log

The first wrapper backgrounds the process. The second wrapper monitors for the exit code and logs it to a file. Be careful that this script will overwrite the same log file each time it is executed.

  • Thank you. It was difficult to pick an 'accepted' answer here! – dotancohen Aug 25 '13 at 12:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.