Commit 356bedd2 authored by Victor Stinner's avatar Victor Stinner

asyncio, Tulip issue #136: Add get/set_debug() methods to BaseEventLoopTests.

Add also a PYTHONASYNCIODEBUG environment variable to debug coroutines since
Python startup, to be able to debug coroutines defined directly in the asyncio
module.
parent 298704d9
.. currentmodule:: asyncio .. currentmodule:: asyncio
.. _asyncio-dev:
Develop with asyncio Develop with asyncio
==================== ====================
...@@ -81,10 +83,10 @@ Detect coroutine objects never scheduled ...@@ -81,10 +83,10 @@ Detect coroutine objects never scheduled
When a coroutine function is called but not passed to :func:`async` or to the 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. :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 To detect such bug, set the environment variable :envvar:`PYTHONASYNCIODEBUG`
coroutine object is destroyed by the garbage collector, a log will be emitted to ``1``. When the coroutine object is destroyed by the garbage collector, a
with the traceback where the coroutine function was called. See the log will be emitted with the traceback where the coroutine function was called.
:ref:`asyncio logger <asyncio-logger>`. See the :ref:`asyncio logger <asyncio-logger>`.
The debug flag changes the behaviour of the :func:`coroutine` decorator. The 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 debug flag value is only used when then coroutine function is defined, not when
......
...@@ -553,6 +553,22 @@ pool of processes). By default, an event loop uses a thread pool executor ...@@ -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`. 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 <asyncio-dev>` section.
Server Server
------ ------
......
...@@ -614,6 +614,14 @@ conflict. ...@@ -614,6 +614,14 @@ conflict.
.. versionadded:: 3.4 .. 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 Debug-mode variables
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
......
...@@ -123,6 +123,7 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -123,6 +123,7 @@ class BaseEventLoop(events.AbstractEventLoop):
self._running = False self._running = False
self._clock_resolution = time.get_clock_info('monotonic').resolution self._clock_resolution = time.get_clock_info('monotonic').resolution
self._exception_handler = None self._exception_handler = None
self._debug = False
def _make_socket_transport(self, sock, protocol, waiter=None, *, def _make_socket_transport(self, sock, protocol, waiter=None, *,
extra=None, server=None): extra=None, server=None):
...@@ -795,3 +796,9 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -795,3 +796,9 @@ class BaseEventLoop(events.AbstractEventLoop):
if not handle._cancelled: if not handle._cancelled:
handle._run() handle._run()
handle = None # Needed to break cycles when an exception occurs. 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
...@@ -345,6 +345,14 @@ class AbstractEventLoop: ...@@ -345,6 +345,14 @@ class AbstractEventLoop:
def call_exception_handler(self, context): def call_exception_handler(self, context):
raise NotImplementedError raise NotImplementedError
# Debug flag management.
def get_debug(self):
raise NotImplementedError
def set_debug(self, enabled):
raise NotImplementedError
class AbstractEventLoopPolicy: class AbstractEventLoopPolicy:
"""Abstract policy for accessing the event loop.""" """Abstract policy for accessing the event loop."""
......
...@@ -12,6 +12,8 @@ import concurrent.futures ...@@ -12,6 +12,8 @@ import concurrent.futures
import functools import functools
import inspect import inspect
import linecache import linecache
import os
import sys
import traceback import traceback
import weakref import weakref
...@@ -28,7 +30,8 @@ from .log import logger ...@@ -28,7 +30,8 @@ from .log import logger
# before you define your coroutines. A downside of using this feature # before you define your coroutines. A downside of using this feature
# is that tracebacks show entries for the CoroWrapper.__next__ method # is that tracebacks show entries for the CoroWrapper.__next__ method
# when _DEBUG is true. # when _DEBUG is true.
_DEBUG = False _DEBUG = (not sys.flags.ignore_environment
and bool(os.environ.get('PYTHONASYNCIODEBUG')))
class CoroWrapper: class CoroWrapper:
......
...@@ -197,6 +197,12 @@ class BaseEventLoopTests(unittest.TestCase): ...@@ -197,6 +197,12 @@ class BaseEventLoopTests(unittest.TestCase):
self.assertEqual([h2], self.loop._scheduled) self.assertEqual([h2], self.loop._scheduled)
self.assertTrue(self.loop._process_events.called) 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.time')
@unittest.mock.patch('asyncio.base_events.logger') @unittest.mock.patch('asyncio.base_events.logger')
def test__run_once_logging(self, m_logger, m_time): def test__run_once_logging(self, m_logger, m_time):
......
"""Tests for tasks.py.""" """Tests for tasks.py."""
import gc import gc
import os.path
import unittest import unittest
from test.script_helper import assert_python_ok
import asyncio import asyncio
from asyncio import test_utils from asyncio import test_utils
...@@ -1461,6 +1463,32 @@ class GatherTestsBase: ...@@ -1461,6 +1463,32 @@ class GatherTestsBase:
cb.assert_called_once_with(fut) cb.assert_called_once_with(fut)
self.assertEqual(fut.result(), [3, 1, exc, exc2]) 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): class FutureGatherTests(GatherTestsBase, unittest.TestCase):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment