From b6007aa68c5f759dd7fdee15a428297b54ecfd33 Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Sat, 30 Mar 2019 08:13:37 -0400 Subject: [PATCH] Add Windows workaround for forceful subprocess termination. Signed-off-by: William D. Jones --- sbysrc/sby_core.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/sbysrc/sby_core.py b/sbysrc/sby_core.py index fb21d17..69d043c 100644 --- a/sbysrc/sby_core.py +++ b/sbysrc/sby_core.py @@ -114,14 +114,22 @@ class SbyTask: if self.running: if not self.silent: self.job.log("{}: terminating process".format(self.info)) - if os.name == "posix": + if os.name != "posix": + # self.p.terminate does not actually terminate underlying + # processes on Windows, so use taskkill to kill the shell + # and children. This for some reason does not cause the + # associated future (self.fut) to complete until it is awaited + # on one last time. + subprocess.Popen("taskkill /T /F /PID {}".format(self.p.pid), stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + else: try: os.killpg(self.p.pid, signal.SIGTERM) except PermissionError: pass self.p.terminate() self.job.tasks_running.remove(self) - all_tasks_running.remove(self) + self.job.tasks_retired.append(self) self.terminated = True async def output_async(self): @@ -159,6 +167,7 @@ class SbyTask: async def shutdown_and_notify(self): self.job.log("%s: finished (returncode=%d)" % (self.info, self.p.returncode)) self.job.tasks_running.remove(self) + self.job.tasks_retired.append(self) self.running = False self.handle_exit(self.p.returncode) @@ -207,6 +216,7 @@ class SbyJob: self.tasks_running = [] self.tasks_pending = [] + self.tasks_retired = [] self.start_clock_time = time() @@ -272,6 +282,14 @@ class SbyJob: if self.opt_timeout is not None: timer_fut.cancel() + # Required on Windows. I am unsure why, but subprocesses that were + # terminated will not have their futures complete until awaited on + # one last time. + if os.name != "posix": + for t in self.tasks_retired: + if not t.fut.done(): + await t.fut + def log(self, logmessage): tm = localtime() print("SBY {:2d}:{:02d}:{:02d} [{}] {}".format(tm.tm_hour, tm.tm_min, tm.tm_sec, self.workdir, logmessage), flush=True)