diff --git a/Doc/library/asyncio-dev.rst b/Doc/library/asyncio-dev.rst index 90bae8440ee..c5f0d1a3e6f 100644 --- a/Doc/library/asyncio-dev.rst +++ b/Doc/library/asyncio-dev.rst @@ -1,5 +1,7 @@ .. currentmodule:: asyncio +.. _asyncio-dev: + Develop with asyncio ==================== @@ -81,10 +83,10 @@ Detect coroutine objects never scheduled When a coroutine function is called but not passed to :func:`async` or to the :class:`Task` constructor, it is not scheduled and it is probably a bug. -To detect such bug, set :data:`asyncio.tasks._DEBUG` to ``True``. When the -coroutine object is destroyed by the garbage collector, a log will be emitted -with the traceback where the coroutine function was called. See the -:ref:`asyncio logger `. +To detect such bug, set the environment variable :envvar:`PYTHONASYNCIODEBUG` +to ``1``. When the coroutine object is destroyed by the garbage collector, a +log will be emitted with the traceback where the coroutine function was called. +See the :ref:`asyncio logger `. The debug flag changes the behaviour of the :func:`coroutine` decorator. The debug flag value is only used when then coroutine function is defined, not when diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index f056cac1758..04b182b9c0c 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -553,6 +553,22 @@ pool of processes). By default, an event loop uses a thread pool executor Set the default executor used by :meth:`run_in_executor`. +Debug mode +---------- + +.. method:: BaseEventLoop.get_debug() + + Get the debug mode (:class:`bool`) of the event loop. + +.. method:: BaseEventLoop.set_debug(enabled: bool) + + Set the debug mode of the event loop. + +.. seealso:: + + The :ref:`Develop with asyncio ` section. + + Server ------ diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 4807a3688c4..0c3c2037e8e 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -614,6 +614,14 @@ conflict. .. versionadded:: 3.4 +.. envvar:: PYTHONASYNCIODEBUG + + If this environment variable is set to a non-empty string, enable the debug + mode of the :mod:`asyncio` module. + + .. versionadded:: 3.4 + + Debug-mode variables ~~~~~~~~~~~~~~~~~~~~ diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index b94ba079684..69caa4d7237 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -123,6 +123,7 @@ class BaseEventLoop(events.AbstractEventLoop): self._running = False self._clock_resolution = time.get_clock_info('monotonic').resolution self._exception_handler = None + self._debug = False def _make_socket_transport(self, sock, protocol, waiter=None, *, extra=None, server=None): @@ -795,3 +796,9 @@ class BaseEventLoop(events.AbstractEventLoop): if not handle._cancelled: handle._run() handle = None # Needed to break cycles when an exception occurs. + + def get_debug(self): + return self._debug + + def set_debug(self, enabled): + self._debug = enabled diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 1030c045bd8..5362f056471 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -345,6 +345,14 @@ class AbstractEventLoop: def call_exception_handler(self, context): raise NotImplementedError + # Debug flag management. + + def get_debug(self): + raise NotImplementedError + + def set_debug(self, enabled): + raise NotImplementedError + class AbstractEventLoopPolicy: """Abstract policy for accessing the event loop.""" diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index a3e7cdf11e4..cf7b54007bb 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -12,6 +12,8 @@ import concurrent.futures import functools import inspect import linecache +import os +import sys import traceback import weakref @@ -28,7 +30,8 @@ from .log import logger # before you define your coroutines. A downside of using this feature # is that tracebacks show entries for the CoroWrapper.__next__ method # when _DEBUG is true. -_DEBUG = False +_DEBUG = (not sys.flags.ignore_environment + and bool(os.environ.get('PYTHONASYNCIODEBUG'))) class CoroWrapper: diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 2eee3be3a87..784a39f8fc0 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -197,6 +197,12 @@ class BaseEventLoopTests(unittest.TestCase): self.assertEqual([h2], self.loop._scheduled) self.assertTrue(self.loop._process_events.called) + def test_set_debug(self): + self.loop.set_debug(True) + self.assertTrue(self.loop.get_debug()) + self.loop.set_debug(False) + self.assertFalse(self.loop.get_debug()) + @unittest.mock.patch('asyncio.base_events.time') @unittest.mock.patch('asyncio.base_events.logger') def test__run_once_logging(self, m_logger, m_time): diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index f27b9522c73..6d03dc78f9f 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1,7 +1,9 @@ """Tests for tasks.py.""" import gc +import os.path import unittest +from test.script_helper import assert_python_ok import asyncio from asyncio import test_utils @@ -1461,6 +1463,32 @@ class GatherTestsBase: cb.assert_called_once_with(fut) self.assertEqual(fut.result(), [3, 1, exc, exc2]) + def test_env_var_debug(self): + path = os.path.dirname(asyncio.__file__) + path = os.path.normpath(os.path.join(path, '..')) + code = '\n'.join(( + 'import sys', + 'sys.path.insert(0, %r)' % path, + 'import asyncio.tasks', + 'print(asyncio.tasks._DEBUG)')) + + # Test with -E to not fail if the unit test was run with + # PYTHONASYNCIODEBUG set to a non-empty string + sts, stdout, stderr = assert_python_ok('-E', '-c', code) + self.assertEqual(stdout.rstrip(), b'False') + + sts, stdout, stderr = assert_python_ok('-c', code, + PYTHONASYNCIODEBUG='') + self.assertEqual(stdout.rstrip(), b'False') + + sts, stdout, stderr = assert_python_ok('-c', code, + PYTHONASYNCIODEBUG='1') + self.assertEqual(stdout.rstrip(), b'True') + + sts, stdout, stderr = assert_python_ok('-E', '-c', code, + PYTHONASYNCIODEBUG='1') + self.assertEqual(stdout.rstrip(), b'False') + class FutureGatherTests(GatherTestsBase, unittest.TestCase):