Skip to content

bpo-34037: Fix test_asyncio failure and add loop.shutdown_default_executor() #15735

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6ed9465
Add flag BaseEventLoop._executor_shutdown_called
aeros Sep 11, 2019
8608e97
Add abstract method for loop.shutdown_default_executor()
aeros Sep 11, 2019
7f39c4a
Add BaseEventLoop method shutdown_default_executor()
aeros Sep 11, 2019
09a9458
Remove whitespace
aeros Sep 11, 2019
c12cf4f
Add BaseEventLoop internal method _check_executor_shutdown()
aeros Sep 11, 2019
afa09a5
Adjust docstring for abstract method loop.shutdown_default_executor
aeros Sep 11, 2019
3303753
Rename method _check_executor_shutdown() to _check_default_executor()
aeros Sep 11, 2019
b877e76
Add default executor check to loop.run_in_executor()
aeros Sep 11, 2019
1a29d83
Implement loop.shutdown_default_executors() in asyncio.run()
aeros Sep 11, 2019
6bde87c
Remove empty line
aeros Sep 11, 2019
1e4a858
Shorten `loop.shutdown_default_executor()` docstring
aeros Sep 11, 2019
8a4a285
Add _shutdown_default_executor() to reduce duplicate code
aeros Sep 11, 2019
f6280f7
Update documentation
aeros Sep 11, 2019
69c7244
Convert `loop.shutdown_default_executor` into a coroutine
aeros Sep 11, 2019
7608920
Add internal method `_do_shutdown()` and fix flag in `shutdown_defaul…
aeros Sep 11, 2019
93ddce2
Update docstrings to reflect changes to `loop.shutdown_default_executor`
aeros Sep 11, 2019
895cad0
Misc fixes for `loop._do_shutdown()`
aeros Sep 11, 2019
94e7af4
Fix whitespace in `asyncio-eventloop.rst`
aeros Sep 11, 2019
7e4980c
Update `asyncio.run()` for `loop.shutdown_default_executor()` corouti…
aeros Sep 11, 2019
626117d
Fix `loop.shutdown_default_executor()` and whitespace
aeros Sep 11, 2019
5340a5c
Delete accidentally committed temp file
aeros Sep 11, 2019
260cace
📜🤖 Added by blurb_it.
blurb-it[bot] Sep 11, 2019
d2bc6d0
Adjust wording in Misc/NEWS entry
aeros Sep 13, 2019
8820584
Adjust wording in doc for loop.shutdown_default_executor()
aeros Sep 13, 2019
7fe0fb1
Merge branch 'bpo-34037' of https://github.com/aeros167/cpython into …
aeros Sep 13, 2019
dbc08ae
Fix doc for loop.shutdown_default_executor()
aeros Sep 13, 2019
fbae3ec
Add "Patch by" to news entry
aeros Sep 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Doc/library/asyncio-eventloop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ Running and stopping the loop

.. versionadded:: 3.6

.. coroutinemethod:: loop.shutdown_default_executor()

Schedule the closure of the default executor and wait for it to join all of
the threads in the :class:`ThreadPoolExecutor`. After calling this method, a
:exc:`RuntimeError` will be raised if :meth:`loop.run_in_executor` is called
while using the default executor.

Note that there is no need to call this function when
:func:`asyncio.run` is used.

.. versionadded:: 3.9


Scheduling callbacks
^^^^^^^^^^^^^^^^^^^^
Expand Down
6 changes: 4 additions & 2 deletions Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ Running an asyncio Program
.. function:: run(coro, \*, debug=False)

This function runs the passed coroutine, taking care of
managing the asyncio event loop and *finalizing asynchronous
generators*.
managing the asyncio event loop, *finalizing asynchronous
generators*, and closing the threadpool.

This function cannot be called when another asyncio event loop is
running in the same thread.
Expand All @@ -229,6 +229,8 @@ Running an asyncio Program
**Important:** this function has been added to asyncio in
Python 3.7 on a :term:`provisional basis <provisional api>`.

.. versionchanged:: 3.9
Updated to use :meth:`loop.shutdown_default_executor`.

Creating Tasks
==============
Expand Down
29 changes: 29 additions & 0 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ def __init__(self):
self._asyncgens = weakref.WeakSet()
# Set to True when `loop.shutdown_asyncgens` is called.
self._asyncgens_shutdown_called = False
# Set to True when `loop.shutdown_default_executor` is called.
self._executor_shutdown_called = False

def __repr__(self):
return (
Expand Down Expand Up @@ -503,6 +505,10 @@ def _check_closed(self):
if self._closed:
raise RuntimeError('Event loop is closed')

def _check_default_executor(self):
if self._executor_shutdown_called:
raise RuntimeError('Executor shutdown has been called')

def _asyncgen_finalizer_hook(self, agen):
self._asyncgens.discard(agen)
if not self.is_closed():
Expand Down Expand Up @@ -543,6 +549,26 @@ async def shutdown_asyncgens(self):
'asyncgen': agen
})

async def shutdown_default_executor(self):
"""Schedule the shutdown of the default executor."""
self._executor_shutdown_called = True
if self._default_executor is None:
return
future = self.create_future()
thread = threading.Thread(target=self._do_shutdown, args=(future,))
thread.start()
try:
await future
finally:
thread.join()

def _do_shutdown(self, future):
try:
self._default_executor.shutdown(wait=True)
self.call_soon_threadsafe(future.set_result, None)
except Exception as ex:
self.call_soon_threadsafe(future.set_exception, ex)

def run_forever(self):
"""Run until stop() is called."""
self._check_closed()
Expand Down Expand Up @@ -632,6 +658,7 @@ def close(self):
self._closed = True
self._ready.clear()
self._scheduled.clear()
self._executor_shutdown_called = True
executor = self._default_executor
if executor is not None:
self._default_executor = None
Expand Down Expand Up @@ -768,6 +795,8 @@ def run_in_executor(self, executor, func, *args):
self._check_callback(func, 'run_in_executor')
if executor is None:
executor = self._default_executor
# Only check when the default executor is being used
self._check_default_executor()
if executor is None:
executor = concurrent.futures.ThreadPoolExecutor()
self._default_executor = executor
Expand Down
4 changes: 4 additions & 0 deletions Lib/asyncio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ async def shutdown_asyncgens(self):
"""Shutdown all active asynchronous generators."""
raise NotImplementedError

async def shutdown_default_executor(self):
"""Schedule the shutdown of the default executor."""
raise NotImplementedError

# Methods scheduling callbacks. All these return Handles.

def _timer_handle_cancelled(self, handle):
Expand Down
1 change: 1 addition & 0 deletions Lib/asyncio/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ async def main():
try:
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
loop.run_until_complete(loop.shutdown_default_executor())
finally:
events.set_event_loop(None)
loop.close()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
For :mod:`asyncio`, add a new coroutine :meth:`loop.shutdown_default_executor`.
The new coroutine provides an API to schedule an executor shutdown that waits
on the threadpool to finish closing. Also, :func:`asyncio.run` has been updated
to utilize the new coroutine. Patch by Kyle Stanley.