Skip to content

Commit 4edcee8

Browse files
committed
Fix process interaction. Repeated signals kill the process.
Fixes #9 Signed-off-by: Pedro Algarvio <[email protected]>
1 parent e0edc12 commit 4edcee8

File tree

4 files changed

+30
-8
lines changed

4 files changed

+30
-8
lines changed

changelog/9.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed process interaction

changelog/9.improvement.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Repeated caught signals now kill the process

src/ptscripts/parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def run(
138138
check=True,
139139
no_output_timeout_secs: int | None = None,
140140
capture: bool = False,
141+
interactive: bool = False,
141142
) -> CompletedProcess[str]:
142143
"""
143144
Run a subprocess.
@@ -147,6 +148,7 @@ def run(
147148
check=check,
148149
no_output_timeout_secs=no_output_timeout_secs,
149150
capture=capture,
151+
interactive=interactive,
150152
)
151153

152154
@contextmanager

src/ptscripts/process.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sys
1111
from datetime import datetime
1212
from datetime import timedelta
13+
from functools import partial
1314
from typing import cast
1415
from typing import TYPE_CHECKING
1516

@@ -131,21 +132,35 @@ def protocol_factory():
131132
return Process(transport, protocol, loop, no_output_timeout_secs=no_output_timeout_secs)
132133

133134

135+
def _handle_signal(proc, sig):
136+
if sig in proc._handled_signals:
137+
log.info(f"\nCaught {sig.name} again, killing the process ...")
138+
proc.kill()
139+
return
140+
log.info(
141+
f"\nCaught {sig.name}, terminating process ....\nSend {sig.name} again to kill the process."
142+
)
143+
proc._handled_signals.append(sig)
144+
proc.terminate()
145+
146+
134147
async def _subprocess_run(
135-
f,
148+
future,
136149
cmdline,
137150
check=True,
138151
no_output_timeout_secs: int | None = None,
139152
capture: bool = False,
153+
interactive: bool = False,
140154
):
141155
stdout = subprocess.PIPE
142156
stderr = subprocess.PIPE
143157
kwargs = {}
144-
# Run in a separate program group
145-
if sys.platform.startswith("win"):
146-
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
147-
else:
148-
kwargs["preexec_fn"] = os.setpgrp
158+
if interactive is False:
159+
# Run in a separate program group
160+
if sys.platform.startswith("win"):
161+
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
162+
else:
163+
kwargs["preexec_fn"] = os.setpgrp
149164
proc = await _create_subprocess_exec(
150165
*cmdline,
151166
stdout=stdout,
@@ -156,25 +171,27 @@ async def _subprocess_run(
156171
capture=capture,
157172
**kwargs,
158173
)
174+
proc._handled_signals = []
159175
loop = asyncio.get_running_loop()
160176
for signame in ("SIGINT", "SIGTERM"):
161177
sig = getattr(signal, signame)
162-
loop.add_signal_handler(sig, proc.terminate)
178+
loop.add_signal_handler(sig, partial(_handle_signal, proc, sig))
163179
stdout, stderr = await asyncio.shield(proc.communicate())
164180
result = subprocess.CompletedProcess(
165181
args=cmdline,
166182
stdout=stdout,
167183
stderr=stderr,
168184
returncode=proc.returncode,
169185
)
170-
f.set_result(result)
186+
future.set_result(result)
171187

172188

173189
def run(
174190
*cmdline,
175191
check=True,
176192
no_output_timeout_secs: int | None = None,
177193
capture: bool = False,
194+
interactive: bool = False,
178195
) -> subprocess.CompletedProcess[str]:
179196
"""
180197
Run a command.
@@ -189,6 +206,7 @@ def run(
189206
check,
190207
no_output_timeout_secs=no_output_timeout_secs,
191208
capture=capture,
209+
interactive=interactive,
192210
)
193211
)
194212
result = future.result()

0 commit comments

Comments
 (0)