Search code examples
pythontwistedtwisted.internet

How can I get exit code of a twisted spawn process which may be terminated unexpected?


I'm using twisted to spawn a local process, which may be terminated in some condition.

I have a custom twisted.internet.protocol.ProcessProtocol class for the reactor. If the local process abruptly terminated, I can't get the return value in processEnded. The exitCode is set to None.

A mcv example is like this:

from twisted.internet import error,protocol,reactor

class MyPP(protocol.ProcessProtocol):
    def processEnded(self, reason):
        if reason.check(error.ProcessTerminated):
            err_info = "wrong termination: %s; exitCode: %s; signal: %s" % \
                        (reason, reason.value.exitCode, reason.value.signal)
            print(err_info)
        else:
            print("processEnded, status %d" % (reason.value.exitCode,))
            print("quitting")
        reactor.stop()

pp = MyPP()
reactor.spawnProcess(pp, "throw_exception", ["throw_exception"], {})
reactor.run()

And the throw_exception executable could be compiled from:

#include <iostream>
int main() {
    throw std::runtime_error("some exception");
    return 0;
}

Execute the python example will print

wrong termination: [Failure instance: Traceback (failure with no frames): : A process has ended with a probable error condition: process ended by signal 6. ]; exitCode: None; signal: 6

The C++ example will have a return value of 134 if run in a shell, which means SIGABRT(6) sent. (I've also tested sending SIGINT to terminate and still getting no exit code.)

How can I get it in the ProcessProtocal instance? Or it is impossible?


Solution

  • In your example, 134 is the "wait status" (or, in the man pages, the "wstatus"). It encodes a few pieces of information about a running-state-transition the process being wait() on underwent.

    The possible transitions are:

    • exited with a code (ie, called exit(2))
    • was killed by a signal
    • whether a core dump was produced
    • was stopped (eg by SIGSTOP)
    • was un-stopped (eg with SIGCONT)

    POSIX provides macros for extracting the details from the wait status: WIFEXITED, WIFSIGNALED, WTERMSIG, WEXITSTATUS, etc.

    Python exposes these through the os module: os.WIFEXITED, etc.

    os.WIFEXITED(134) evaluates to False. os.WIFSIGNALED(134) evaluates to true and os.WTERMSIG(134) evaluates to 6.

    ProcessDone and ProcessTerminated use these macros to extract the information and present it in a slightly more useful form - the exitCode and signal attributes.

    POSIX doesn't provide a way to go the other way, though. There's no standard API for constructing a "wait status" representing, say, "the process was killed by signal 6".

    Fortunately, partially because there's no way to go backwards, Twisted preserves the original wait status on the exception as the status attribute. If you check that attribute on the exception wrapped in the Failure passed to your ProcessProtocol, you should find the wait status you're looking for.