Skip to content

Commit f0a945d

Browse files
committed
Cleanup refs to callback in Handle/TimerHandle on cancel
Currently there's a lot of potential to create reference cycles between callback handles and protocols/transports that use them. Basically any bound-method has a potential to create such a cycle. This happens especially often for TimerHandles used to ensure timeouts. Fix that by ensuring that the handle clears the ref to its callback when the 'cancel()' method is called.
1 parent 8462981 commit f0a945d

File tree

1 file changed

+20
-13
lines changed

1 file changed

+20
-13
lines changed

uvloop/cbhandles.pyx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ cdef class Handle:
5656

5757
if cb_type == 1:
5858
callback = self.arg1
59+
if callback is None:
60+
raise RuntimeError(
61+
'cannot run Handle; callback is not set')
62+
5963
args = self.arg2
6064

6165
if args is None:
@@ -106,11 +110,11 @@ cdef class Handle:
106110
cdef _cancel(self):
107111
self._cancelled = 1
108112
self.callback = NULL
109-
self.arg2 = self.arg3 = self.arg4 = None
113+
self.arg1 = self.arg2 = self.arg3 = self.arg4 = None
110114

111115
cdef _format_handle(self):
112116
# Mirrors `asyncio.base_events._format_handle`.
113-
if self.cb_type == 1:
117+
if self.cb_type == 1 and self.arg1 is not None:
114118
cb = self.arg1
115119
if isinstance(getattr(cb, '__self__', None), aio_Task):
116120
try:
@@ -135,7 +139,7 @@ cdef class Handle:
135139
if self._cancelled:
136140
info.append('cancelled')
137141

138-
if self.cb_type == 1:
142+
if self.cb_type == 1 and self.arg1 is not None:
139143
func = self.arg1
140144
# Cython can unset func.__qualname__/__name__, hence the checks.
141145
if hasattr(func, '__qualname__') and func.__qualname__:
@@ -146,7 +150,7 @@ cdef class Handle:
146150
cb_name = repr(func)
147151

148152
info.append(cb_name)
149-
else:
153+
elif self.meth_name is not None:
150154
info.append(self.meth_name)
151155

152156
if self._source_traceback is not None:
@@ -214,6 +218,7 @@ cdef class TimerHandle:
214218
if self.timer is None:
215219
return
216220

221+
self.callback = None
217222
self.args = None
218223

219224
try:
@@ -225,10 +230,11 @@ cdef class TimerHandle:
225230
cdef _run(self):
226231
if self._cancelled == 1:
227232
return
233+
if self.callback is None:
234+
raise RuntimeError('cannot run TimerHandle; callback is not set')
228235

229236
callback = self.callback
230237
args = self.args
231-
self._clear()
232238

233239
Py_INCREF(self) # Since _run is a cdef and there's no BoundMethod,
234240
# we guard 'self' manually.
@@ -275,15 +281,16 @@ cdef class TimerHandle:
275281
if self._cancelled:
276282
info.append('cancelled')
277283

278-
func = self.callback
279-
if hasattr(func, '__qualname__'):
280-
cb_name = getattr(func, '__qualname__')
281-
elif hasattr(func, '__name__'):
282-
cb_name = getattr(func, '__name__')
283-
else:
284-
cb_name = repr(func)
284+
if self.callback is not None:
285+
func = self.callback
286+
if hasattr(func, '__qualname__'):
287+
cb_name = getattr(func, '__qualname__')
288+
elif hasattr(func, '__name__'):
289+
cb_name = getattr(func, '__name__')
290+
else:
291+
cb_name = repr(func)
285292

286-
info.append(cb_name)
293+
info.append(cb_name)
287294

288295
if self._source_traceback is not None:
289296
frame = self._source_traceback[-1]

0 commit comments

Comments
 (0)