[?1034hdiff -r 513eea89b80a Lib/asyncio/futures.py --- a/Lib/asyncio/futures.py Thu Jun 19 12:59:32 2014 +0200 +++ b/Lib/asyncio/futures.py Thu Jun 19 16:42:18 2014 +0200 @@ -169,6 +169,9 @@ class Future: res += '<{}>'.format(self._state) 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: def __del__(self): if not self._log_traceback: diff -r 513eea89b80a Lib/asyncio/tasks.py --- a/Lib/asyncio/tasks.py Thu Jun 19 12:59:32 2014 +0200 +++ b/Lib/asyncio/tasks.py Thu Jun 19 16:42:18 2014 +0200 @@ -32,6 +32,7 @@ from .log import logger _DEBUG = (not sys.flags.ignore_environment and bool(os.environ.get('PYTHONASYNCIODEBUG'))) +_PY34 = (sys.version_info >= (3, 4)) _PY35 = (sys.version_info >= (3, 5)) @@ -181,6 +182,18 @@ class Task(futures.Future): self._loop.call_soon(self._step) 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): res = super().__repr__() if (self._must_cancel and diff -r 513eea89b80a Lib/test/test_asyncio/test_tasks.py --- a/Lib/test/test_asyncio/test_tasks.py Thu Jun 19 12:59:32 2014 +0200 +++ b/Lib/test/test_asyncio/test_tasks.py Thu Jun 19 16:42:18 2014 +0200 @@ -5,7 +5,9 @@ import sys import types import unittest import weakref +from test import support from test.script_helper import assert_python_ok +from unittest import mock import asyncio from asyncio import tasks @@ -1501,9 +1503,43 @@ class TaskTests(test_utils.TestCase): def test_corowrapper_weakref(self): wd = weakref.WeakValueDictionary() def foo(): yield from [] - cw = asyncio.tasks.CoroWrapper(foo(), foo) - wd['cw'] = cw # Would fail without __weakref__ slot. - cw.gen = None # Suppress warning from __del__. + + 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: