I'm trying to make something like supervisor for my python daemon process and found out that same code works in python2 and doesn't work in python3.
Generally, I've come to this minimal example code.
daemon.py
#!/usr/bin/env python
import signal
import sys
import os
def stop(*args, **kwargs):
    print('daemon exited', os.getpid())
    sys.exit(0)
signal.signal(signal.SIGTERM, stop)
print('daemon started', os.getpid())
while True:
    pass
supervisor.py
import os
import signal
import subprocess
from time import sleep
parent_pid = os.getpid()
commands = [
    [
        './daemon.py'
    ]
]
popen_list = []
for command in commands:
    popen = subprocess.Popen(command, preexec_fn=os.setsid)
    popen_list.append(popen)
def stop_workers(*args, **kwargs):
    for popen in popen_list:
        print('send_signal', popen.pid)
        popen.send_signal(signal.SIGTERM)
        while True:
            popen_return_code = popen.poll()
            if popen_return_code is not None:
                break
            sleep(5)
signal.signal(signal.SIGTERM, stop_workers)
for popen in popen_list:
    print('wait_main', popen.wait())
If you run supervisor.py and then call kill -15 on its pid, then it will hang in infinite loop, because popen_return_code will never be not None. I discovered, that it's basically because of adding threading.Lock for wait_pid operation (source), but how can I rewrite code so it'll handle child exit correctly?