How to terminate a thread subprocess call and thread exit in Python?

Viewed 870

I am trying to run subprocess Popen in a thread in Python. The command in Popen is expected to run continuously to collect logs. But when a condition is met outside the thread, I want to stop the Popen subprocess and the corresponding thread also to finish. Below is a sample representative code:

import threading
import subprocess

class MyClass(threading.Thread):
    def __init__(self):
        super(MyClass, self).__init__()
    
    def run(self):
        self.proc = subprocess.Popen("while true; do foo; sleep 2; done", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = self.proc.communicate()
            
myclass = MyClass()
myclass.start()

myclass.proc.kill()



print("Done")

But in the above code, it gets stuck forever. What is the correct way to stop the running Popen subprocess and also to finish the thread?

2 Answers

Add param preexec_fn=os.setsid to your Popen func.

Use os.killpg(proc.pid, signal.SIGKILL) to kill sub process by pid.

All you really need to do is to have the thread, i.e. the MyClass instance, do the "killing" and instantiate MyClass with an event that it will wait on and then when set will kill the process. As I do not know what is in foo, I have substituted a simple echo hello for that and have set text=True on the Popen call so that the output is Unicode rather than bytes:

import threading
import subprocess
import time

class MyClass(threading.Thread):
    def __init__(self, evt):
        super(MyClass, self).__init__()
        self.evt = evt

    def run(self):
        proc = subprocess.Popen("while true; do echo hello; sleep 2; done", shell=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # wait for the "kill" order:
        self.evt.wait()
        proc.kill()
        stdout, stderr = proc.communicate()
        # for demo purposes let's see what has been output:
        print(stdout, end='')

evt = threading.Event()
myclass = MyClass(evt)
myclass.start()

# let the process run for 5 seconds and then give the kill order:
time.sleep(5)
print("Killing process:")
evt.set()
myclass.join()
print("Done")

Prints:

Killing process:
hello
hello
hello
Done
Related