summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/asyncio-eventloop.rst11
-rw-r--r--Doc/library/asyncio-runner.rst6
-rw-r--r--Lib/asyncio/base_events.py17
-rw-r--r--Lib/asyncio/constants.py3
-rw-r--r--Lib/asyncio/runners.py13
-rw-r--r--Misc/NEWS.d/next/Library/2019-09-25-00-37-51.bpo-38267.X9Jb5V.rst3
6 files changed, 44 insertions, 9 deletions
diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst
index b61ffd5..0a960ab 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -180,18 +180,27 @@ Running and stopping the loop
.. versionadded:: 3.6
-.. coroutinemethod:: loop.shutdown_default_executor()
+.. coroutinemethod:: loop.shutdown_default_executor(timeout=None)
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.
+ The *timeout* parameter specifies the amount of time the executor will
+ be given to finish joining. The default value is ``None``, which means the
+ executor will be given an unlimited amount of time.
+
+ If the timeout duration is reached, a warning is emitted and executor is
+ terminated without waiting for its threads to finish joining.
+
Note that there is no need to call this function when
:func:`asyncio.run` is used.
.. versionadded:: 3.9
+ .. versionchanged:: 3.12
+ Added the *timeout* parameter.
Scheduling callbacks
^^^^^^^^^^^^^^^^^^^^
diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 4abe7b6..c43d664 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -28,7 +28,7 @@ Running an asyncio Program
This function runs the passed coroutine, taking care of
managing the asyncio event loop, *finalizing asynchronous
- generators*, and closing the threadpool.
+ generators*, and closing the executor.
This function cannot be called when another asyncio event loop is
running in the same thread.
@@ -41,6 +41,10 @@ Running an asyncio Program
the end. It should be used as a main entry point for asyncio
programs, and should ideally only be called once.
+ The executor is given a timeout duration of 5 minutes to shutdown.
+ If the executor hasn't finished within that duration, a warning is
+ emitted and the executor is closed.
+
Example::
async def main():
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index a675fff..9c9d98d 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -561,8 +561,13 @@ class BaseEventLoop(events.AbstractEventLoop):
'asyncgen': agen
})
- async def shutdown_default_executor(self):
- """Schedule the shutdown of the default executor."""
+ async def shutdown_default_executor(self, timeout=None):
+ """Schedule the shutdown of the default executor.
+
+ The timeout parameter specifies the amount of time the executor will
+ be given to finish joining. The default value is None, which means
+ that the executor will be given an unlimited amount of time.
+ """
self._executor_shutdown_called = True
if self._default_executor is None:
return
@@ -572,7 +577,13 @@ class BaseEventLoop(events.AbstractEventLoop):
try:
await future
finally:
- thread.join()
+ thread.join(timeout)
+
+ if thread.is_alive():
+ warnings.warn("The executor did not finishing joining "
+ f"its threads within {timeout} seconds.",
+ RuntimeWarning, stacklevel=2)
+ self._default_executor.shutdown(wait=False)
def _do_shutdown(self, future):
try:
diff --git a/Lib/asyncio/constants.py b/Lib/asyncio/constants.py
index f171ead..f0ce043 100644
--- a/Lib/asyncio/constants.py
+++ b/Lib/asyncio/constants.py
@@ -26,6 +26,9 @@ SENDFILE_FALLBACK_READBUFFER_SIZE = 1024 * 256
FLOW_CONTROL_HIGH_WATER_SSL_READ = 256 # KiB
FLOW_CONTROL_HIGH_WATER_SSL_WRITE = 512 # KiB
+# Default timeout for joining the threads in the threadpool
+THREAD_JOIN_TIMEOUT = 300
+
# The enum should be here to break circular dependencies between
# base_events and sslproto
class _SendfileMode(enum.Enum):
diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 840b133..b1c4dbd 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -9,7 +9,7 @@ from . import coroutines
from . import events
from . import exceptions
from . import tasks
-
+from . import constants
class _State(enum.Enum):
CREATED = "created"
@@ -69,7 +69,8 @@ class Runner:
loop = self._loop
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
- loop.run_until_complete(loop.shutdown_default_executor())
+ loop.run_until_complete(
+ loop.shutdown_default_executor(constants.THREAD_JOIN_TIMEOUT))
finally:
if self._set_event_loop:
events.set_event_loop(None)
@@ -160,8 +161,8 @@ def run(main, *, debug=None):
"""Execute the coroutine and return the result.
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 default executor.
This function cannot be called when another asyncio event loop is
running in the same thread.
@@ -172,6 +173,10 @@ def run(main, *, debug=None):
It should be used as a main entry point for asyncio programs, and should
ideally only be called once.
+ The executor is given a timeout duration of 5 minutes to shutdown.
+ If the executor hasn't finished within that duration, a warning is
+ emitted and the executor is closed.
+
Example:
async def main():
diff --git a/Misc/NEWS.d/next/Library/2019-09-25-00-37-51.bpo-38267.X9Jb5V.rst b/Misc/NEWS.d/next/Library/2019-09-25-00-37-51.bpo-38267.X9Jb5V.rst
new file mode 100644
index 0000000..b842fdc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-09-25-00-37-51.bpo-38267.X9Jb5V.rst
@@ -0,0 +1,3 @@
+Add *timeout* parameter to :meth:`asyncio.loop.shutdown_default_executor`.
+The default value is ``None``, which means the executor will be given an unlimited amount of time.
+When called from :class:`asyncio.Runner` or :func:`asyncio.run`, the default timeout is 5 minutes.