Skip to content

Commit 41a93fa

Browse files
committed
asyncio: sync with Tulip
- Issues #21936, #21163: Fix sporadic failures of test_future_exception_never_retrieved() - Handle.cancel() now clears references to callback and args - In debug mode, repr(Handle) now contains the location where the Handle was created.
1 parent e4d9bb6 commit 41a93fa

File tree

3 files changed

+74
-16
lines changed

3 files changed

+74
-16
lines changed

Lib/asyncio/events.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,20 @@ def __init__(self, callback, args, loop):
8282
self._source_traceback = None
8383

8484
def __repr__(self):
85-
info = []
85+
info = [self.__class__.__name__]
8686
if self._cancelled:
8787
info.append('cancelled')
88-
info.append(_format_callback(self._callback, self._args))
89-
return '<%s %s>' % (self.__class__.__name__, ' '.join(info))
88+
if self._callback is not None:
89+
info.append(_format_callback(self._callback, self._args))
90+
if self._source_traceback:
91+
frame = self._source_traceback[-1]
92+
info.append('created at %s:%s' % (frame[0], frame[1]))
93+
return '<%s>' % ' '.join(info)
9094

9195
def cancel(self):
9296
self._cancelled = True
97+
self._callback = None
98+
self._args = None
9399

94100
def _run(self):
95101
try:
@@ -125,7 +131,11 @@ def __repr__(self):
125131
if self._cancelled:
126132
info.append('cancelled')
127133
info.append('when=%s' % self._when)
128-
info.append(_format_callback(self._callback, self._args))
134+
if self._callback is not None:
135+
info.append(_format_callback(self._callback, self._args))
136+
if self._source_traceback:
137+
frame = self._source_traceback[-1]
138+
info.append('created at %s:%s' % (frame[0], frame[1]))
129139
return '<%s %s>' % (self.__class__.__name__, ' '.join(info))
130140

131141
def __hash__(self):

Lib/test/test_asyncio/test_events.py

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,27 +1810,30 @@ def test_handle_weakref(self):
18101810
wd['h'] = h # Would fail without __weakref__ slot.
18111811

18121812
def test_handle_repr(self):
1813+
self.loop.get_debug.return_value = False
1814+
18131815
# simple function
1814-
h = asyncio.Handle(noop, (), self.loop)
1815-
src = test_utils.get_function_source(noop)
1816+
h = asyncio.Handle(noop, (1, 2), self.loop)
1817+
filename, lineno = test_utils.get_function_source(noop)
18161818
self.assertEqual(repr(h),
1817-
'<Handle noop() at %s:%s>' % src)
1819+
'<Handle noop(1, 2) at %s:%s>'
1820+
% (filename, lineno))
18181821

18191822
# cancelled handle
18201823
h.cancel()
18211824
self.assertEqual(repr(h),
1822-
'<Handle cancelled noop() at %s:%s>' % src)
1825+
'<Handle cancelled>')
18231826

18241827
# decorated function
18251828
cb = asyncio.coroutine(noop)
18261829
h = asyncio.Handle(cb, (), self.loop)
18271830
self.assertEqual(repr(h),
1828-
'<Handle noop() at %s:%s>' % src)
1831+
'<Handle noop() at %s:%s>'
1832+
% (filename, lineno))
18291833

18301834
# partial function
18311835
cb = functools.partial(noop, 1, 2)
18321836
h = asyncio.Handle(cb, (3,), self.loop)
1833-
filename, lineno = src
18341837
regex = (r'^<Handle noop\(1, 2\)\(3\) at %s:%s>$'
18351838
% (re.escape(filename), lineno))
18361839
self.assertRegex(repr(h), regex)
@@ -1839,16 +1842,33 @@ def test_handle_repr(self):
18391842
if sys.version_info >= (3, 4):
18401843
method = HandleTests.test_handle_repr
18411844
cb = functools.partialmethod(method)
1842-
src = test_utils.get_function_source(method)
1845+
filename, lineno = test_utils.get_function_source(method)
18431846
h = asyncio.Handle(cb, (), self.loop)
18441847

1845-
filename, lineno = src
18461848
cb_regex = r'<function HandleTests.test_handle_repr .*>'
18471849
cb_regex = (r'functools.partialmethod\(%s, , \)\(\)' % cb_regex)
18481850
regex = (r'^<Handle %s at %s:%s>$'
18491851
% (cb_regex, re.escape(filename), lineno))
18501852
self.assertRegex(repr(h), regex)
18511853

1854+
def test_handle_repr_debug(self):
1855+
self.loop.get_debug.return_value = True
1856+
1857+
# simple function
1858+
create_filename = __file__
1859+
create_lineno = sys._getframe().f_lineno + 1
1860+
h = asyncio.Handle(noop, (1, 2), self.loop)
1861+
filename, lineno = test_utils.get_function_source(noop)
1862+
self.assertEqual(repr(h),
1863+
'<Handle noop(1, 2) at %s:%s created at %s:%s>'
1864+
% (filename, lineno, create_filename, create_lineno))
1865+
1866+
# cancelled handle
1867+
h.cancel()
1868+
self.assertEqual(repr(h),
1869+
'<Handle cancelled created at %s:%s>'
1870+
% (create_filename, create_lineno))
1871+
18521872
def test_handle_source_traceback(self):
18531873
loop = asyncio.get_event_loop_policy().new_event_loop()
18541874
loop.set_debug(True)
@@ -1894,7 +1914,7 @@ def test_timer(self):
18941914
def callback(*args):
18951915
return args
18961916

1897-
args = ()
1917+
args = (1, 2, 3)
18981918
when = time.monotonic()
18991919
h = asyncio.TimerHandle(when, callback, args, mock.Mock())
19001920
self.assertIs(h._callback, callback)
@@ -1904,14 +1924,17 @@ def callback(*args):
19041924
# cancel
19051925
h.cancel()
19061926
self.assertTrue(h._cancelled)
1907-
1927+
self.assertIsNone(h._callback)
1928+
self.assertIsNone(h._args)
19081929

19091930
# when cannot be None
19101931
self.assertRaises(AssertionError,
19111932
asyncio.TimerHandle, None, callback, args,
19121933
self.loop)
19131934

19141935
def test_timer_repr(self):
1936+
self.loop.get_debug.return_value = False
1937+
19151938
# simple function
19161939
h = asyncio.TimerHandle(123, noop, (), self.loop)
19171940
src = test_utils.get_function_source(noop)
@@ -1921,8 +1944,27 @@ def test_timer_repr(self):
19211944
# cancelled handle
19221945
h.cancel()
19231946
self.assertEqual(repr(h),
1924-
'<TimerHandle cancelled when=123 noop() at %s:%s>'
1925-
% src)
1947+
'<TimerHandle cancelled when=123>')
1948+
1949+
def test_timer_repr_debug(self):
1950+
self.loop.get_debug.return_value = True
1951+
1952+
# simple function
1953+
create_filename = __file__
1954+
create_lineno = sys._getframe().f_lineno + 1
1955+
h = asyncio.TimerHandle(123, noop, (), self.loop)
1956+
filename, lineno = test_utils.get_function_source(noop)
1957+
self.assertEqual(repr(h),
1958+
'<TimerHandle when=123 noop() '
1959+
'at %s:%s created at %s:%s>'
1960+
% (filename, lineno, create_filename, create_lineno))
1961+
1962+
# cancelled handle
1963+
h.cancel()
1964+
self.assertEqual(repr(h),
1965+
'<TimerHandle cancelled when=123 created at %s:%s>'
1966+
% (create_filename, create_lineno))
1967+
19261968

19271969
def test_timer_comparison(self):
19281970
def callback(*args):

Lib/test/test_asyncio/test_futures.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,12 @@ def test_future_source_traceback(self):
299299

300300
@mock.patch('asyncio.base_events.logger')
301301
def test_future_exception_never_retrieved(self, m_log):
302+
# FIXME: Python issue #21163, other tests may "leak" pending task which
303+
# emit a warning when they are destroyed by the GC
304+
support.gc_collect()
305+
m_log.error.reset_mock()
306+
# ---
307+
302308
self.loop.set_debug(True)
303309

304310
def memory_error():

0 commit comments

Comments
 (0)