kfx
kfx

Reputation: 8537

Popen does not return in Python 2.7

I'm developing a process scheduler in Python. The idea is to create several threads from the main function and start an external process in each of these threads. The external process should continue to run until either it's finished or the main thread decides to stop it (by sending a kill signal) because the process' CPU time limit is exceeded.

The problem is that sometimes the Popen call blocks and fails to return. This code reproduces the problem with ~50% probability on my system (Ubuntu 14.04.3 LTS):

import os, time, threading, sys
from subprocess import Popen

class Process:
    def __init__(self, args):
        self.args = args

    def run(self):
        print("Run subprocess: " + " ".join(self.args))
        retcode = -1
        try:
                self.process = Popen(self.args)
                print("started a process")
                while self.process.poll() is None:
                    # in the real code, check for the end condition here and send kill signal if required
                    time.sleep(1.0)
                retcode = self.process.returncode
        except:
            print("unexpected error:", sys.exc_info()[0])

        print("process done, returned {}".format(retcode))
        return retcode

def main():
    processes = [Process(["/bin/cat"]) for _ in range(4)]
    # start all processes
    for p in processes:
        t = threading.Thread(target=Process.run, args=(p,))
        t.daemon = True
        t.start()
    print("all threads started")
    # wait for Ctrl+C
    while True:
        time.sleep(1.0)

main()

The output indicates that only 3 Popen() calls have returned:

Run subprocess: /bin/cat
Run subprocess: /bin/cat
Run subprocess: /bin/cat
Run subprocess: /bin/cat
 started a process
started a process
started a process
all threads started

However, running ps shows that all four processes have in fact been started!

The problem does not show up when using Python 3.4, but I want to keep Python 2.7 compatibility.

Edit: the problem also goes away if I add some delay before starting each subsequent thread.

Edit 2: I did a bit of investigation and the blocking is caused by line 1308 in subprocess.py module, which tries to do some reading from a pipe in the parent process:

   data = _eintr_retry_call(os.read, errpipe_read, 1048576)

Upvotes: 2

Views: 323

Answers (1)

Ben Mosher
Ben Mosher

Reputation: 13381

There are a handful of bugs in python 2.7's subprocess module that can result in deadlock when calling the Popen constructor from multiple threads. They are fixed in later versions of Python, 3.2+ IIRC.

You may find that using the subprocess32 backport of Python 3.2/3.3's subprocess module resolves your issue.

*I was unable to locate the link to the actual bug report, but encountered it recently when dealing with a similar issue.

Upvotes: 1

Related Questions