Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python losing control of subprocess?

I'm using a commercial application that uses Python as part of its scripting API. One of the functions provided is something called App.run(). When this function is called, it starts a new Java process that does the rest of the execution. (Unfortunately, I don't really know what it's doing under the hood as the supplied Python modules are .pyc files, and many of the Python functions are SWIG generated).

The trouble I'm having is that I'm building the App.run() call into a larger Python application that needs to do some guaranteed cleanup code (closing a database, etc.). Unfortunately, if the subprocess is interrupted with Ctrl+C, it aborts and returns to the command line without returning control to the main Python program. Thus, my cleanup code never executes.

So far I've tried:

  1. Registering a function with atexit... doesn't work
  2. Putting cleanup in a class __del__ destructor... doesn't work. (App.run() is inside the class)
  3. Creating a signal handler for Ctrl+C in the main Python app... doesn't work
  4. Putting App.run() in a Thread... results in a Memory Fault after the Ctrl+C
  5. Putting App.run() in a Process (from multiprocessing)... doesn't work

Any ideas what could be happening?

like image 716
jasonm76 Avatar asked Oct 17 '12 18:10

jasonm76


People also ask

Should you use subprocess Python?

Using subprocesses in Python, you can also obtain exit codes and input, output, or error streams. The Subprocess in Python can be useful if you've ever intended to streamline your command-line scripting or utilize Python alongside command-line apps—or any applications, for that matter.

What does Popen return Python?

Returned value If successful, popen() returns a pointer to an open stream that can be used to read or write to a pipe. If unsuccessful, popen() returns a NULL pointer and sets errno to one of the following values: Error Code. Description.

How can we avoid shell true in subprocess?

From the docs: args is required for all calls and should be a string, or a sequence of program arguments. Providing a sequence of arguments is generally preferred, as it allows the module to take care of any required escaping and quoting of arguments (e.g. to permit spaces in file names).


2 Answers

This is just an outline- but something like this?

import os

cpid = os.fork()
if not cpid:
    # change stdio handles etc
    os.setsid() # Probably not needed
    App.run()
    os._exit(0)

os.waitpid(cpid)
# clean up here

(os.fork is *nix only)

The same idea could be implemented with subprocess in an OS agnostic way. The idea is running App.run() in a child process and then waiting for the child process to exit; regardless of how the child process died. On posix, you could also trap for SIGCHLD (Child process death). I'm not a windows guru, so if applicable and subprocess doesn't work, someone else will have to chime in here.

After App.run() is called, I'd be curious what the process tree looks like. It's possible its running an exec and taking over the python process space. If thats happening, creating a child process is the only way I can think of trapping it.

like image 151
tMC Avatar answered Nov 15 '22 15:11

tMC


If try: App.run() finally: cleanup() doesn't work; you could try to run it in a subprocess:

import sys
from subprocess import call

rc = call([sys.executable, 'path/to/run_app.py'])
cleanup()

Or if you have the code in a string you could use -c option e.g.:

rc = call([sys.executable, '-c', '''import sys
print(sys.argv)
'''])

You could implement @tMC's suggestion using subprocess by adding preexec_fn=os.setsid argument (note: no ()) though I don't see how creating a process group might help here. Or you could try shell=True argument to run it in a separate shell.

You might give another try to multiprocessing:

import multiprocessing as mp

if __name__=="__main__":
   p = mp.Process(target=App.run)
   p.start()
   p.join()
   cleanup()
like image 35
jfs Avatar answered Nov 15 '22 16:11

jfs