diff -r 66c7f30fe8c7 Lib/asyncio/tasks.py --- a/Lib/asyncio/tasks.py Sun Aug 17 23:01:33 2014 -0500 +++ b/Lib/asyncio/tasks.py Mon Aug 18 21:31:30 2014 +0200 @@ -41,6 +41,12 @@ # all running event loops. {EventLoop: Task} _current_tasks = {} + # Strong refs to all currently waited-for futures. Needed to avoid tasks + # being garabage collected when the future has no strong refs outside the + # task itself and the task has no strong refs either. + _all_futures = set() + + @classmethod def current_task(cls, loop=None): """Return the currently running task in an event loop or None. @@ -250,6 +256,8 @@ result._blocking = False result.add_done_callback(self._wakeup) self._fut_waiter = result + # keep a strong ref to the future, so we don't loose tasks + self.__class__._all_futures.add(result) if self._must_cancel: if self._fut_waiter.cancel(): self._must_cancel = False @@ -277,10 +285,11 @@ RuntimeError( 'Task got bad yield: {!r}'.format(result))) finally: - self.__class__._current_tasks.pop(self._loop) + del self.__class__._current_tasks[self._loop] self = None # Needed to break cycles when an exception occurs. def _wakeup(self, future): + self.__class__._all_futures.discard(future) try: value = future.result() except Exception as exc: diff -r 66c7f30fe8c7 Lib/test/test_asyncio/test_tasks.py --- a/Lib/test/test_asyncio/test_tasks.py Sun Aug 17 23:01:33 2014 -0500 +++ b/Lib/test/test_asyncio/test_tasks.py Mon Aug 18 21:31:30 2014 +0200 @@ -1681,6 +1681,99 @@ 'test_task_source_traceback')) self.loop.run_until_complete(task) + @unittest.skipUnless(PY34, + 'need python 3.4 or later') + def test_gc_pending_task(self): + + # This future will become unreferenced later on + future = asyncio.Future(loop=self.loop) + + @asyncio.coroutine + def gc_me(loop, _future): + yield from _future + # at this point, the only reference to gc_me() task is + # the Task._wakeup() method in future._callbacks + return 1 + + # schedule the task + coro = gc_me(self.loop) + task = asyncio.async(coro, loop=self.loop, future) + 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) + self.assertEqual(len(asyncio.Task._all_futures), 1) + self.assertEqual(asyncio.Task.all_tasks(loop=self.loop), {task}) + + # Complete the future used in gc_me(), and remove all references + coro = None + source_traceback = task._source_traceback + task = None + future.set_result(True) + future = None + + # asyncio holds a strong ref to the future and prevents GC of gc_me() + support.gc_collect() + self.assertEqual(len(asyncio.Task._all_futures), 1) + self.assertEqual(len(asyncio.Task.all_tasks(loop=self.loop)), 1) + + # execute the task again so it terminates + self.loop._run_once() + self.assertEqual(len(self.loop._ready), 0) + + # asyncio does not hold a strong ref to the future once it is done + support.gc_collect() + + self.assertEqual(len(asyncio.Task._all_futures), 0) + self.assertEqual(asyncio.Task.all_tasks(loop=self.loop), set()) + + @unittest.skipUnless(PY34, + 'need python 3.4 or later') + def test_gc_cancelled_task(self): + + # This future will become unreferenced later on + future = asyncio.Future(loop=self.loop) + + @asyncio.coroutine + def cancel_me(loop, _future): + yield from _future + # at this point, the only reference to kill_me() task is + # the Task._wakeup() method in future._callbacks + + # schedule the task + coro = cancel_me(self.loop) + task = asyncio.async(coro, loop=self.loop, future) + 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) + self.assertEqual(len(asyncio.Task._all_futures), 1) + self.assertEqual(asyncio.Task.all_tasks(loop=self.loop), {task}) + + # Complete the future used in kill_me(), and remove all references + coro = None + source_traceback = task._source_traceback + task = None + future.set_result(True) + future = None + + task = asyncio.Task.all_tasks(loop=self.loop)[0] + task.cancel() + task = None + + # execute the task again so it terminates + self.loop._run_once() + self.assertEqual(len(asyncio.Task._all_futures), 0) + self.assertEqual(len(self.loop._ready), 0) + + # asyncio does not hold a strong ref to the future once it is done + support.gc_collect() + + self.assertEqual(len(asyncio.Task._all_futures), 0) + self.assertEqual(asyncio.Task.all_tasks(loop=self.loop), set()) + class GatherTestsBase: