Skip to content

Commit ebba633

Browse files
committed
Fix subprocess.close() to let its processes die gracefully
Fixes #128.
1 parent 0e5db37 commit ebba633

File tree

3 files changed

+41
-1
lines changed

3 files changed

+41
-1
lines changed

tests/test_process.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,32 @@ def cancel_make_transport():
580580
self.loop.run_until_complete(cancel_make_transport())
581581
tb.run_briefly(self.loop)
582582

583+
def test_close_gets_process_closed(self):
584+
585+
loop = self.loop
586+
587+
class Protocol(asyncio.SubprocessProtocol):
588+
589+
def __init__(self):
590+
self.closed = loop.create_future()
591+
592+
def connection_lost(self, exc):
593+
self.closed.set_result(1)
594+
595+
@asyncio.coroutine
596+
def test_subprocess():
597+
transport, protocol = yield from loop.subprocess_exec(
598+
Protocol, *self.PROGRAM_BLOCKED)
599+
pid = transport.get_pid()
600+
transport.close()
601+
self.assertIsNone(transport.get_returncode())
602+
yield from protocol.closed
603+
self.assertIsNotNone(transport.get_returncode())
604+
with self.assertRaises(ProcessLookupError):
605+
os.kill(pid, 0)
606+
607+
loop.run_until_complete(test_subprocess())
608+
583609

584610
class Test_UV_Process(_TestProcess, tb.UVTestCase):
585611

uvloop/handles/process.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ cdef class UVProcess(UVHandle):
2020
char *uv_opt_file
2121
bytes __cwd
2222

23+
bint _kill_requested
24+
2325
cdef _init(self, Loop loop, list args, dict env, cwd,
2426
start_new_session,
2527
_stdin, _stdout, _stderr, pass_fds,

uvloop/handles/process.pyx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ cdef class UVProcess(UVHandle):
1010
self._fds_to_close = set()
1111
self._preexec_fn = None
1212
self._restore_signals = True
13+
self._kill_requested = False
1314

1415
cdef _init(self, Loop loop, list args, dict env,
1516
cwd, start_new_session,
@@ -303,6 +304,8 @@ cdef class UVProcess(UVHandle):
303304
cdef _kill(self, int signum):
304305
cdef int err
305306
self._ensure_alive()
307+
if signum in {uv.SIGKILL, uv.SIGTERM}:
308+
self._kill_requested = True
306309
err = uv.uv_process_kill(<uv.uv_process_t*>self._handle, signum)
307310
if err < 0:
308311
raise convert_error(err)
@@ -532,6 +535,11 @@ cdef class UVProcessTransport(UVProcess):
532535
else:
533536
self._pending_calls.append((_CALL_CONNECTION_LOST, None, None))
534537

538+
cdef _warn_unclosed(self):
539+
if self._kill_requested:
540+
return
541+
super()._warn_unclosed()
542+
535543
def __stdio_inited(self, waiter, stdio_fut):
536544
exc = stdio_fut.exception()
537545
if exc is not None:
@@ -628,7 +636,11 @@ cdef class UVProcessTransport(UVProcess):
628636
if self._stderr is not None:
629637
self._stderr.close()
630638

631-
self._close()
639+
if self._returncode is not None:
640+
# The process is dead, just close the UV handle
641+
# (otherwise, the process should have been killed already
642+
# and we're just waiting for a SIGCHLD.)
643+
self._close()
632644

633645
def get_extra_info(self, name, default=None):
634646
return default

0 commit comments

Comments
 (0)