Skip to content

Commit 2ddb480

Browse files
committed
Fix a memory leak related to contextvars support. (#192)
Initial patch and memleak discovery by Victor K. @hellysmile. Fixes #191.
1 parent 2690ff6 commit 2ddb480

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

tests/test_context.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import random
44
import sys
55
import unittest
6+
import weakref
67

78
from uvloop import _testbase as tb
89

@@ -117,6 +118,29 @@ async def main():
117118

118119
self.assertEqual(cvar.get(), -1)
119120

121+
@unittest.skipUnless(PY37, 'requires Python 3.7')
122+
def test_task_context_4(self):
123+
import contextvars
124+
cvar = contextvars.ContextVar('cvar', default='nope')
125+
126+
class TrackMe:
127+
pass
128+
tracked = TrackMe()
129+
ref = weakref.ref(tracked)
130+
131+
async def sub():
132+
cvar.set(tracked)
133+
self.loop.call_soon(lambda: None)
134+
135+
async def main():
136+
await self.loop.create_task(sub())
137+
138+
task = self.loop.create_task(main())
139+
self.loop.run_until_complete(task)
140+
141+
del tracked
142+
self.assertIsNone(ref())
143+
120144

121145
class Test_UV_Context(_ContextBaseTests, tb.UVTestCase):
122146

uvloop/cbhandles.pyx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ cdef class Handle:
1515
self._source_traceback = extract_stack()
1616

1717
cdef inline _set_context(self, object context):
18+
cdef PyContext* current_context
19+
1820
if PY37:
1921
if context is None:
20-
context = <object>PyContext_CopyCurrent()
22+
context = copy_current_context()
2123
self.context = context
2224
else:
2325
if context is not None:
@@ -179,7 +181,7 @@ cdef class TimerHandle:
179181

180182
if PY37:
181183
if context is None:
182-
context = <object>PyContext_CopyCurrent()
184+
context = copy_current_context()
183185
self.context = context
184186
else:
185187
if context is not None:
@@ -400,3 +402,15 @@ cdef extract_stack():
400402

401403
stack.reverse()
402404
return stack
405+
406+
407+
cdef copy_current_context():
408+
cdef PyContext* current_context
409+
410+
if PY37:
411+
current_context = PyContext_CopyCurrent()
412+
py_context = <object>current_context
413+
Py_XDECREF(<PyObject*>current_context)
414+
return py_context
415+
416+
raise NotImplementedError('"contextvars" support requires Python 3.7+')

0 commit comments

Comments
 (0)