|
|
import atexit |
|
|
import logging |
|
|
import multiprocessing |
|
|
import os |
|
|
import platform |
|
|
import signal |
|
|
from subprocess import PIPE |
|
|
from subprocess import Popen |
|
|
import sys |
|
|
|
|
|
|
|
|
CREATE_NEW_PROCESS_GROUP = 0x00000200 |
|
|
DETACHED_PROCESS = 0x00000008 |
|
|
|
|
|
REGISTERED = [] |
|
|
|
|
|
|
|
|
def start_detached(executable, *args): |
|
|
""" |
|
|
Starts a fully independent subprocess (with no parent) |
|
|
:param executable: executable |
|
|
:param args: arguments to the executable, eg: ['--param1_key=param1_val', '-vvv' ...] |
|
|
:return: pid of the grandchild process |
|
|
""" |
|
|
|
|
|
|
|
|
reader, writer = multiprocessing.Pipe(False) |
|
|
|
|
|
|
|
|
process = multiprocessing.Process( |
|
|
target=_start_detached, |
|
|
args=(executable, *args), |
|
|
kwargs={"writer": writer}, |
|
|
daemon=True, |
|
|
) |
|
|
process.start() |
|
|
process.join() |
|
|
|
|
|
pid = reader.recv() |
|
|
REGISTERED.append(pid) |
|
|
|
|
|
writer.close() |
|
|
reader.close() |
|
|
process.close() |
|
|
|
|
|
return pid |
|
|
|
|
|
|
|
|
def _start_detached(executable, *args, writer: multiprocessing.Pipe = None): |
|
|
|
|
|
kwargs = {} |
|
|
if platform.system() == "Windows": |
|
|
kwargs.update(creationflags=DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP) |
|
|
elif sys.version_info < (3, 2): |
|
|
|
|
|
kwargs.update(preexec_fn=os.setsid) |
|
|
else: |
|
|
kwargs.update(start_new_session=True) |
|
|
|
|
|
|
|
|
p = Popen([executable, *args], stdin=PIPE, stdout=PIPE, stderr=PIPE, **kwargs) |
|
|
|
|
|
|
|
|
writer.send(p.pid) |
|
|
sys.exit() |
|
|
|
|
|
|
|
|
def _cleanup(): |
|
|
for pid in REGISTERED: |
|
|
try: |
|
|
logging.getLogger(__name__).debug("cleaning up pid %d " % pid) |
|
|
os.kill(pid, signal.SIGTERM) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
atexit.register(_cleanup) |
|
|
|