Skip to content

Commit 67ea618

Browse files
author
Yury Selivanov
committed
Merge 3.5 (issue #28368)
2 parents 3ae4155 + 9eb6c67 commit 67ea618

4 files changed

Lines changed: 42 additions & 6 deletions

File tree

Lib/asyncio/unix_events.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ class BaseChildWatcher(AbstractChildWatcher):
748748

749749
def __init__(self):
750750
self._loop = None
751+
self._callbacks = {}
751752

752753
def close(self):
753754
self.attach_loop(None)
@@ -761,6 +762,12 @@ def _do_waitpid_all(self):
761762
def attach_loop(self, loop):
762763
assert loop is None or isinstance(loop, events.AbstractEventLoop)
763764

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

@@ -809,10 +816,6 @@ class SafeChildWatcher(BaseChildWatcher):
809816
big number of children (O(n) each time SIGCHLD is raised)
810817
"""
811818

812-
def __init__(self):
813-
super().__init__()
814-
self._callbacks = {}
815-
816819
def close(self):
817820
self._callbacks.clear()
818821
super().close()
@@ -824,6 +827,11 @@ def __exit__(self, a, b, c):
824827
pass
825828

826829
def add_child_handler(self, pid, callback, *args):
830+
if self._loop is None:
831+
raise RuntimeError(
832+
"Cannot add child handler, "
833+
"the child watcher does not have a loop attached")
834+
827835
self._callbacks[pid] = (callback, args)
828836

829837
# Prevent a race condition in case the child is already terminated.
@@ -888,7 +896,6 @@ class FastChildWatcher(BaseChildWatcher):
888896
"""
889897
def __init__(self):
890898
super().__init__()
891-
self._callbacks = {}
892899
self._lock = threading.Lock()
893900
self._zombies = {}
894901
self._forks = 0
@@ -920,6 +927,12 @@ def __exit__(self, a, b, c):
920927

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

Lib/test/test_asyncio/test_subprocess.py

Lines changed: 7 additions & 0 deletions
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

Lib/test/test_asyncio/test_unix_events.py

Lines changed: 13 additions & 1 deletion
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):

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ Library
163163
- Issue #27759: Fix selectors incorrectly retain invalid file descriptors.
164164
Patch by Mark Williams.
165165

166+
- Issue #28368: Refuse monitoring processes if the child watcher has no
167+
loop attached.
168+
Patch by Vincent Michel.
169+
166170
Windows
167171
-------
168172

0 commit comments

Comments
 (0)