|
36 | 36 | import queue |
37 | 37 | import re |
38 | 38 | import shutil |
| 39 | +import signal |
39 | 40 | import subprocess |
40 | 41 | import sys |
41 | 42 | import threading |
@@ -3997,19 +3998,29 @@ def run_piece(self, anchor: Block, piece: Block): |
3997 | 3998 | label = describe_block(piece) |
3998 | 3999 |
|
3999 | 4000 | def worker(): |
4000 | | - kw = dict(capture_output=True, text=True, encoding="utf-8", |
4001 | | - errors="replace", cwd=folder, timeout=PIECE_TIMEOUT, |
4002 | | - input="") |
4003 | | - if IS_WINDOWS: |
4004 | | - kw["creationflags"] = NO_WINDOW |
| 4001 | + kw = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| 4002 | + stdin=subprocess.PIPE, text=True, encoding="utf-8", |
| 4003 | + errors="replace", cwd=folder) |
| 4004 | + kw.update(spawn_kwargs()) |
4005 | 4005 | try: |
4006 | | - res = subprocess.run(command, **kw) |
4007 | | - out, err, code = res.stdout, res.stderr, res.returncode |
4008 | | - except subprocess.TimeoutExpired as slow: |
4009 | | - out = (slow.stdout or "") |
4010 | | - if isinstance(out, bytes): |
4011 | | - out = out.decode("utf-8", "replace") |
4012 | | - err, code = "", -9 |
| 4006 | + proc = subprocess.Popen(command, **kw) |
| 4007 | + except Exception as exc: |
| 4008 | + self.app.ui(lambda: self.piece_done(anchor, label, "", |
| 4009 | + str(exc), -1)) |
| 4010 | + return |
| 4011 | + try: |
| 4012 | + out, err = proc.communicate(input="", timeout=PIECE_TIMEOUT) |
| 4013 | + code = proc.returncode |
| 4014 | + except subprocess.TimeoutExpired: |
| 4015 | + # a block that is still speaking or playing has children of |
| 4016 | + # its own, so the whole tree has to go - otherwise the voice |
| 4017 | + # carries on after we have said we stopped it |
| 4018 | + kill_tree(proc) |
| 4019 | + try: |
| 4020 | + out, err = proc.communicate(timeout=5) |
| 4021 | + except Exception: |
| 4022 | + out, err = "", "" |
| 4023 | + code = -9 |
4013 | 4024 | except Exception as exc: |
4014 | 4025 | out, err, code = "", str(exc), -1 |
4015 | 4026 | self.app.ui(lambda: self.piece_done(anchor, label, out, err, code)) |
@@ -5217,6 +5228,40 @@ def venv_folder_for(settings: Settings, project_folder: str) -> str: |
5217 | 5228 | NO_WINDOW = 0x08000000 if IS_WINDOWS else 0 |
5218 | 5229 |
|
5219 | 5230 |
|
| 5231 | +def spawn_kwargs() -> dict: |
| 5232 | + """Start a program so that it can be stopped along with its children. |
| 5233 | +
|
| 5234 | + A block that speaks or plays a sound starts a helper of its own, and |
| 5235 | + stopping only the Python process would leave the voice talking. |
| 5236 | + """ |
| 5237 | + if IS_WINDOWS: |
| 5238 | + return {"creationflags": NO_WINDOW} |
| 5239 | + return {"start_new_session": True} |
| 5240 | + |
| 5241 | + |
| 5242 | +def kill_tree(proc): |
| 5243 | + """Stop a running program, and anything it started.""" |
| 5244 | + if proc is None or proc.poll() is not None: |
| 5245 | + return |
| 5246 | + if IS_WINDOWS: |
| 5247 | + try: |
| 5248 | + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], |
| 5249 | + capture_output=True, creationflags=NO_WINDOW) |
| 5250 | + return |
| 5251 | + except Exception: |
| 5252 | + pass |
| 5253 | + else: |
| 5254 | + try: |
| 5255 | + os.killpg(os.getpgid(proc.pid), signal.SIGKILL) |
| 5256 | + return |
| 5257 | + except Exception: |
| 5258 | + pass |
| 5259 | + try: |
| 5260 | + proc.kill() |
| 5261 | + except Exception: |
| 5262 | + pass |
| 5263 | + |
| 5264 | + |
5220 | 5265 | def popen_kwargs() -> dict: |
5221 | 5266 | kw = dict(stdout=subprocess.PIPE, stderr=subprocess.STDOUT, |
5222 | 5267 | stdin=subprocess.DEVNULL, text=True, encoding="utf-8", |
@@ -6017,8 +6062,7 @@ def start(self, path: str, cwd: str): |
6017 | 6062 | kw = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
6018 | 6063 | stdin=subprocess.PIPE, text=True, encoding="utf-8", |
6019 | 6064 | errors="replace", bufsize=1, cwd=cwd) |
6020 | | - if IS_WINDOWS: |
6021 | | - kw["creationflags"] = NO_WINDOW |
| 6065 | + kw.update(spawn_kwargs()) |
6022 | 6066 | env = dict(os.environ) |
6023 | 6067 | env["PYTHONUNBUFFERED"] = "1" |
6024 | 6068 | env["PYTHONIOENCODING"] = "utf-8" |
@@ -6070,19 +6114,8 @@ def send(self, text: str): |
6070 | 6114 | def stop(self): |
6071 | 6115 | if not self.running: |
6072 | 6116 | return |
6073 | | - try: |
6074 | | - if IS_WINDOWS: |
6075 | | - subprocess.run(["taskkill", "/F", "/T", "/PID", |
6076 | | - str(self.proc.pid)], |
6077 | | - capture_output=True, |
6078 | | - creationflags=NO_WINDOW) |
6079 | | - else: |
6080 | | - self.proc.terminate() |
6081 | | - except Exception: |
6082 | | - try: |
6083 | | - self.proc.kill() |
6084 | | - except Exception: |
6085 | | - pass |
| 6117 | + # the whole tree, so a voice or a sound it started stops as well |
| 6118 | + kill_tree(self.proc) |
6086 | 6119 | self.q.put(("sys", "--- stopped ---")) |
6087 | 6120 |
|
6088 | 6121 |
|
|
0 commit comments