Skip to content
This repository was archived by the owner on Nov 23, 2017. It is now read-only.

Commit 496f60f

Browse files
Vincent Michel1st1
Vincent Michel
authored andcommitted
Refuse monitoring processes if the child watcher has no loop attached
PR #391.
1 parent 3a8fc97 commit 496f60f

File tree

3 files changed

+38
-6
lines changed

3 files changed

+38
-6
lines changed

asyncio/unix_events.py

+18-5
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ class BaseChildWatcher(AbstractChildWatcher):
746746

747747
def __init__(self):
748748
self._loop = None
749+
self._callbacks = {}
749750

750751
def close(self):
751752
self.attach_loop(None)
@@ -759,6 +760,12 @@ def _do_waitpid_all(self):
759760
def attach_loop(self, loop):
760761
assert loop is None or isinstance(loop, events.AbstractEventLoop)
761762

763+
if self._loop is not None and loop is None and self._callbacks:
764+
warnings.warn(
765+
'A loop is being detached '
766+
'from a child watcher with pending handlers',
767+
RuntimeWarning)
768+
762769
if self._loop is not None:
763770
self._loop.remove_signal_handler(signal.SIGCHLD)
764771

@@ -807,10 +814,6 @@ class SafeChildWatcher(BaseChildWatcher):
807814
big number of children (O(n) each time SIGCHLD is raised)
808815
"""
809816

810-
def __init__(self):
811-
super().__init__()
812-
self._callbacks = {}
813-
814817
def close(self):
815818
self._callbacks.clear()
816819
super().close()
@@ -822,6 +825,11 @@ def __exit__(self, a, b, c):
822825
pass
823826

824827
def add_child_handler(self, pid, callback, *args):
828+
if self._loop is None:
829+
raise RuntimeError(
830+
"Cannot add child handler, "
831+
"the child watcher does not have a loop attached")
832+
825833
self._callbacks[pid] = (callback, args)
826834

827835
# Prevent a race condition in case the child is already terminated.
@@ -886,7 +894,6 @@ class FastChildWatcher(BaseChildWatcher):
886894
"""
887895
def __init__(self):
888896
super().__init__()
889-
self._callbacks = {}
890897
self._lock = threading.Lock()
891898
self._zombies = {}
892899
self._forks = 0
@@ -918,6 +925,12 @@ def __exit__(self, a, b, c):
918925

919926
def add_child_handler(self, pid, callback, *args):
920927
assert self._forks, "Must use the context manager"
928+
929+
if self._loop is None:
930+
raise RuntimeError(
931+
"Cannot add child handler, "
932+
"the child watcher does not have a loop attached")
933+
921934
with self._lock:
922935
try:
923936
returncode = self._zombies.pop(pid)

tests/test_subprocess.py

+7
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,13 @@ def kill_running():
433433
# the transport was not notified yet
434434
self.assertFalse(killed)
435435

436+
# Unlike SafeChildWatcher, FastChildWatcher does not pop the
437+
# callbacks if waitpid() is called elsewhere. Let's clear them
438+
# manually to avoid a warning when the watcher is detached.
439+
if sys.platform != 'win32' and \
440+
isinstance(self, SubprocessFastWatcherTests):
441+
asyncio.get_child_watcher()._callbacks.clear()
442+
436443
def test_popen_error(self):
437444
# Issue #24763: check that the subprocess transport is closed
438445
# when BaseSubprocessTransport fails

tests/test_unix_events.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import tempfile
1212
import threading
1313
import unittest
14+
import warnings
1415
from unittest import mock
1516

1617
if sys.platform == 'win32':
@@ -1391,7 +1392,9 @@ def test_set_loop_race_condition(self, m):
13911392
with mock.patch.object(
13921393
old_loop, "remove_signal_handler") as m_remove_signal_handler:
13931394

1394-
self.watcher.attach_loop(None)
1395+
with self.assertWarnsRegex(
1396+
RuntimeWarning, 'A loop is being detached'):
1397+
self.watcher.attach_loop(None)
13951398

13961399
m_remove_signal_handler.assert_called_once_with(
13971400
signal.SIGCHLD)
@@ -1463,6 +1466,15 @@ def test_close(self, m):
14631466
if isinstance(self.watcher, asyncio.FastChildWatcher):
14641467
self.assertFalse(self.watcher._zombies)
14651468

1469+
@waitpid_mocks
1470+
def test_add_child_handler_with_no_loop_attached(self, m):
1471+
callback = mock.Mock()
1472+
with self.create_watcher() as watcher:
1473+
with self.assertRaisesRegex(
1474+
RuntimeError,
1475+
'the child watcher does not have a loop attached'):
1476+
watcher.add_child_handler(100, callback)
1477+
14661478

14671479
class SafeChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase):
14681480
def create_watcher(self):

0 commit comments

Comments
 (0)