Kaydet (Commit) 777aa64e authored tarafından Victor Stinner's avatar Victor Stinner

(Merge 3.4) asyncio: Log an error if a Task is destroyed while it is still pending

...@@ -169,6 +169,9 @@ class Future: ...@@ -169,6 +169,9 @@ class Future:
res += '<{}>'.format(self._state) res += '<{}>'.format(self._state)
return res return res
# On Python 3.3 or older, objects with a destructor part of a reference
# cycle are never destroyed. It's not more the case on Python 3.4 thanks to
# the PEP 442.
if _PY34: if _PY34:
def __del__(self): def __del__(self):
if not self._log_traceback: if not self._log_traceback:
......
...@@ -32,6 +32,7 @@ from .log import logger ...@@ -32,6 +32,7 @@ from .log import logger
_DEBUG = (not sys.flags.ignore_environment _DEBUG = (not sys.flags.ignore_environment
and bool(os.environ.get('PYTHONASYNCIODEBUG'))) and bool(os.environ.get('PYTHONASYNCIODEBUG')))
_PY34 = (sys.version_info >= (3, 4))
_PY35 = (sys.version_info >= (3, 5)) _PY35 = (sys.version_info >= (3, 5))
...@@ -181,6 +182,18 @@ class Task(futures.Future): ...@@ -181,6 +182,18 @@ class Task(futures.Future):
self._loop.call_soon(self._step) self._loop.call_soon(self._step)
self.__class__._all_tasks.add(self) self.__class__._all_tasks.add(self)
# On Python 3.3 or older, objects with a destructor part of a reference
# cycle are never destroyed. It's not more the case on Python 3.4 thanks to
# the PEP 442.
if _PY34:
def __del__(self):
if self._state == futures._PENDING:
self._loop.call_exception_handler({
'task': self,
'message': 'Task was destroyed but it is pending!',
})
futures.Future.__del__(self)
def __repr__(self): def __repr__(self):
res = super().__repr__() res = super().__repr__()
if (self._must_cancel and if (self._must_cancel and
......
...@@ -244,7 +244,8 @@ class BaseEventLoopTests(test_utils.TestCase): ...@@ -244,7 +244,8 @@ class BaseEventLoopTests(test_utils.TestCase):
@mock.patch('asyncio.base_events.logger') @mock.patch('asyncio.base_events.logger')
def test__run_once_logging(self, m_logger): def test__run_once_logging(self, m_logger):
def slow_select(timeout): def slow_select(timeout):
time.sleep(1.0) # Sleep a bit longer than a second to avoid timer resolution issues.
time.sleep(1.1)
return [] return []
# logging needs debug flag # logging needs debug flag
......
...@@ -5,13 +5,16 @@ import sys ...@@ -5,13 +5,16 @@ import sys
import types import types
import unittest import unittest
import weakref import weakref
from test import support
from test.script_helper import assert_python_ok from test.script_helper import assert_python_ok
from unittest import mock
import asyncio import asyncio
from asyncio import tasks from asyncio import tasks
from asyncio import test_utils from asyncio import test_utils
PY34 = (sys.version_info >= (3, 4))
PY35 = (sys.version_info >= (3, 5)) PY35 = (sys.version_info >= (3, 5))
...@@ -1501,9 +1504,45 @@ class TaskTests(test_utils.TestCase): ...@@ -1501,9 +1504,45 @@ class TaskTests(test_utils.TestCase):
def test_corowrapper_weakref(self): def test_corowrapper_weakref(self):
wd = weakref.WeakValueDictionary() wd = weakref.WeakValueDictionary()
def foo(): yield from [] def foo(): yield from []
cw = asyncio.tasks.CoroWrapper(foo(), foo)
wd['cw'] = cw # Would fail without __weakref__ slot. @unittest.skipUnless(PY34,
cw.gen = None # Suppress warning from __del__. 'need python 3.4 or later')
def test_log_destroyed_pending_task(self):
@asyncio.coroutine
def kill_me(loop):
future = asyncio.Future(loop=loop)
yield from future
# at this point, the only reference to kill_me() task is
# the Task._wakeup() method in future._callbacks
raise Exception("code never reached")
mock_handler = mock.Mock()
self.loop.set_exception_handler(mock_handler)
# schedule the task
coro = kill_me(self.loop)
task = asyncio.async(coro, loop=self.loop)
self.assertEqual(asyncio.Task.all_tasks(loop=self.loop), {task})
# execute the task so it waits for future
self.loop._run_once()
self.assertEqual(len(self.loop._ready), 0)
# remove the future used in kill_me(), and references to the task
del coro.gi_frame.f_locals['future']
coro = None
task = None
# no more reference to kill_me() task: the task is destroyed by the GC
support.gc_collect()
self.assertEqual(asyncio.Task.all_tasks(loop=self.loop), set())
mock_handler.assert_called_with(self.loop, {
'message': 'Task was destroyed but it is pending!',
'task': mock.ANY,
})
mock_handler.reset_mock()
class GatherTestsBase: class GatherTestsBase:
......
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