mirror of https://github.com/python/cpython
Issue #25593: Change semantics of EventLoop.stop(). (Merge 3.5->3.6)
This commit is contained in:
commit
13d9b86d46
|
@ -29,7 +29,16 @@ Run an event loop
|
||||||
|
|
||||||
.. method:: BaseEventLoop.run_forever()
|
.. method:: BaseEventLoop.run_forever()
|
||||||
|
|
||||||
Run until :meth:`stop` is called.
|
Run until :meth:`stop` is called. If :meth:`stop` is called before
|
||||||
|
:meth:`run_forever()` is called, this polls the I/O selector once
|
||||||
|
with a timeout of zero, runs all callbacks scheduled in response to
|
||||||
|
I/O events (and those that were already scheduled), and then exits.
|
||||||
|
If :meth:`stop` is called while :meth:`run_forever` is running,
|
||||||
|
this will run the current batch of callbacks and then exit. Note
|
||||||
|
that callbacks scheduled by callbacks will not run in that case;
|
||||||
|
they will run the next time :meth:`run_forever` is called.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.5.1
|
||||||
|
|
||||||
.. method:: BaseEventLoop.run_until_complete(future)
|
.. method:: BaseEventLoop.run_until_complete(future)
|
||||||
|
|
||||||
|
@ -48,10 +57,10 @@ Run an event loop
|
||||||
|
|
||||||
Stop running the event loop.
|
Stop running the event loop.
|
||||||
|
|
||||||
Every callback scheduled before :meth:`stop` is called will run.
|
This causes :meth:`run_forever` to exit at the next suitable
|
||||||
Callbacks scheduled after :meth:`stop` is called will not run.
|
opportunity (see there for more details).
|
||||||
However, those callbacks will run if :meth:`run_forever` is called
|
|
||||||
again later.
|
.. versionchanged:: 3.5.1
|
||||||
|
|
||||||
.. method:: BaseEventLoop.is_closed()
|
.. method:: BaseEventLoop.is_closed()
|
||||||
|
|
||||||
|
@ -61,7 +70,8 @@ Run an event loop
|
||||||
|
|
||||||
.. method:: BaseEventLoop.close()
|
.. method:: BaseEventLoop.close()
|
||||||
|
|
||||||
Close the event loop. The loop must not be running.
|
Close the event loop. The loop must not be running. Pending
|
||||||
|
callbacks will be lost.
|
||||||
|
|
||||||
This clears the queues and shuts down the executor, but does not wait for
|
This clears the queues and shuts down the executor, but does not wait for
|
||||||
the executor to finish.
|
the executor to finish.
|
||||||
|
|
|
@ -494,7 +494,7 @@ data and wait until the connection is closed::
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
print('The server closed the connection')
|
print('The server closed the connection')
|
||||||
print('Stop the event lop')
|
print('Stop the event loop')
|
||||||
self.loop.stop()
|
self.loop.stop()
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
|
@ -70,10 +70,6 @@ def _format_pipe(fd):
|
||||||
return repr(fd)
|
return repr(fd)
|
||||||
|
|
||||||
|
|
||||||
class _StopError(BaseException):
|
|
||||||
"""Raised to stop the event loop."""
|
|
||||||
|
|
||||||
|
|
||||||
def _check_resolved_address(sock, address):
|
def _check_resolved_address(sock, address):
|
||||||
# Ensure that the address is already resolved to avoid the trap of hanging
|
# Ensure that the address is already resolved to avoid the trap of hanging
|
||||||
# the entire event loop when the address requires doing a DNS lookup.
|
# the entire event loop when the address requires doing a DNS lookup.
|
||||||
|
@ -118,9 +114,6 @@ def _check_resolved_address(sock, address):
|
||||||
"got host %r: %s"
|
"got host %r: %s"
|
||||||
% (host, err))
|
% (host, err))
|
||||||
|
|
||||||
def _raise_stop_error(*args):
|
|
||||||
raise _StopError
|
|
||||||
|
|
||||||
|
|
||||||
def _run_until_complete_cb(fut):
|
def _run_until_complete_cb(fut):
|
||||||
exc = fut._exception
|
exc = fut._exception
|
||||||
|
@ -129,7 +122,7 @@ def _run_until_complete_cb(fut):
|
||||||
# Issue #22429: run_forever() already finished, no need to
|
# Issue #22429: run_forever() already finished, no need to
|
||||||
# stop it.
|
# stop it.
|
||||||
return
|
return
|
||||||
_raise_stop_error()
|
fut._loop.stop()
|
||||||
|
|
||||||
|
|
||||||
class Server(events.AbstractServer):
|
class Server(events.AbstractServer):
|
||||||
|
@ -184,6 +177,7 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._timer_cancelled_count = 0
|
self._timer_cancelled_count = 0
|
||||||
self._closed = False
|
self._closed = False
|
||||||
|
self._stopping = False
|
||||||
self._ready = collections.deque()
|
self._ready = collections.deque()
|
||||||
self._scheduled = []
|
self._scheduled = []
|
||||||
self._default_executor = None
|
self._default_executor = None
|
||||||
|
@ -298,11 +292,11 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
self._thread_id = threading.get_ident()
|
self._thread_id = threading.get_ident()
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
try:
|
|
||||||
self._run_once()
|
self._run_once()
|
||||||
except _StopError:
|
if self._stopping:
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
|
self._stopping = False
|
||||||
self._thread_id = None
|
self._thread_id = None
|
||||||
self._set_coroutine_wrapper(False)
|
self._set_coroutine_wrapper(False)
|
||||||
|
|
||||||
|
@ -345,11 +339,10 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop running the event loop.
|
"""Stop running the event loop.
|
||||||
|
|
||||||
Every callback scheduled before stop() is called will run. Callbacks
|
Every callback already scheduled will still run. This simply informs
|
||||||
scheduled after stop() is called will not run. However, those callbacks
|
run_forever to stop looping after a complete iteration.
|
||||||
will run if run_forever is called again later.
|
|
||||||
"""
|
"""
|
||||||
self.call_soon(_raise_stop_error)
|
self._stopping = True
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Close the event loop.
|
"""Close the event loop.
|
||||||
|
@ -1194,7 +1187,7 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
handle._scheduled = False
|
handle._scheduled = False
|
||||||
|
|
||||||
timeout = None
|
timeout = None
|
||||||
if self._ready:
|
if self._ready or self._stopping:
|
||||||
timeout = 0
|
timeout = 0
|
||||||
elif self._scheduled:
|
elif self._scheduled:
|
||||||
# Compute the desired timeout.
|
# Compute the desired timeout.
|
||||||
|
|
|
@ -71,12 +71,13 @@ def run_until(loop, pred, timeout=30):
|
||||||
|
|
||||||
|
|
||||||
def run_once(loop):
|
def run_once(loop):
|
||||||
"""loop.stop() schedules _raise_stop_error()
|
"""Legacy API to run once through the event loop.
|
||||||
and run_forever() runs until _raise_stop_error() callback.
|
|
||||||
this wont work if test waits for some IO events, because
|
This is the recommended pattern for test code. It will poll the
|
||||||
_raise_stop_error() runs before any of io events callbacks.
|
selector once and run all callbacks scheduled in response to I/O
|
||||||
|
events.
|
||||||
"""
|
"""
|
||||||
loop.stop()
|
loop.call_soon(loop.stop)
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -757,6 +757,59 @@ class BaseEventLoopTests(test_utils.TestCase):
|
||||||
pass
|
pass
|
||||||
self.assertTrue(func.called)
|
self.assertTrue(func.called)
|
||||||
|
|
||||||
|
def test_single_selecter_event_callback_after_stopping(self):
|
||||||
|
# Python issue #25593: A stopped event loop may cause event callbacks
|
||||||
|
# to run more than once.
|
||||||
|
event_sentinel = object()
|
||||||
|
callcount = 0
|
||||||
|
doer = None
|
||||||
|
|
||||||
|
def proc_events(event_list):
|
||||||
|
nonlocal doer
|
||||||
|
if event_sentinel in event_list:
|
||||||
|
doer = self.loop.call_soon(do_event)
|
||||||
|
|
||||||
|
def do_event():
|
||||||
|
nonlocal callcount
|
||||||
|
callcount += 1
|
||||||
|
self.loop.call_soon(clear_selector)
|
||||||
|
|
||||||
|
def clear_selector():
|
||||||
|
doer.cancel()
|
||||||
|
self.loop._selector.select.return_value = ()
|
||||||
|
|
||||||
|
self.loop._process_events = proc_events
|
||||||
|
self.loop._selector.select.return_value = (event_sentinel,)
|
||||||
|
|
||||||
|
for i in range(1, 3):
|
||||||
|
with self.subTest('Loop %d/2' % i):
|
||||||
|
self.loop.call_soon(self.loop.stop)
|
||||||
|
self.loop.run_forever()
|
||||||
|
self.assertEqual(callcount, 1)
|
||||||
|
|
||||||
|
def test_run_once(self):
|
||||||
|
# Simple test for test_utils.run_once(). It may seem strange
|
||||||
|
# to have a test for this (the function isn't even used!) but
|
||||||
|
# it's a de-factor standard API for library tests. This tests
|
||||||
|
# the idiom: loop.call_soon(loop.stop); loop.run_forever().
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
def callback():
|
||||||
|
nonlocal count
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
self.loop._process_events = mock.Mock()
|
||||||
|
self.loop.call_soon(callback)
|
||||||
|
test_utils.run_once(self.loop)
|
||||||
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
|
def test_run_forever_pre_stopped(self):
|
||||||
|
# Test that the old idiom for pre-stopping the loop works.
|
||||||
|
self.loop._process_events = mock.Mock()
|
||||||
|
self.loop.stop()
|
||||||
|
self.loop.run_forever()
|
||||||
|
self.loop._selector.select.assert_called_once_with(0)
|
||||||
|
|
||||||
|
|
||||||
class MyProto(asyncio.Protocol):
|
class MyProto(asyncio.Protocol):
|
||||||
done = None
|
done = None
|
||||||
|
|
|
@ -95,6 +95,8 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #25593: Change semantics of EventLoop.stop() in asyncio.
|
||||||
|
|
||||||
- Issue #6973: When we know a subprocess.Popen process has died, do
|
- Issue #6973: When we know a subprocess.Popen process has died, do
|
||||||
not allow the send_signal(), terminate(), or kill() methods to do
|
not allow the send_signal(), terminate(), or kill() methods to do
|
||||||
anything as they could potentially signal a different process.
|
anything as they could potentially signal a different process.
|
||||||
|
|
Loading…
Reference in New Issue