merge
This commit is contained in:
commit
aba9bfed73
|
@ -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
|
||||||
_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):
|
||||||
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):
|
||||||
@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
|
||||||
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):
|
||||||
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:
|
||||||
|
|
Loading…
Reference in New Issue