File tree Expand file tree Collapse file tree 2 files changed +36
-1
lines changed
Expand file tree Collapse file tree 2 files changed +36
-1
lines changed Original file line number Diff line number Diff line change @@ -134,7 +134,22 @@ def unwrap(self):
134134 # Tracebacks show the 'raise' line below out of context, so let's give
135135 # this variable a name that makes sense out of context.
136136 captured_error = self .error
137- raise captured_error
137+ try :
138+ raise captured_error
139+ finally :
140+ # We want to avoid creating a reference cycle here. Python does
141+ # collect cycles just fine, so it wouldn't be the end of the world
142+ # if we did create a cycle, but the cyclic garbage collector adds
143+ # latency to Python programs, and the more cycles you create, the
144+ # more often it runs, so it's nicer to avoid creating them in the
145+ # first place. For more details see:
146+ #
147+ # https://github.com/python-trio/trio/issues/1770
148+ #
149+ # In particuar, by deleting this local variables from the 'unwrap'
150+ # methods frame, we avoid the 'captured_error' object's
151+ # __traceback__ from indirectly referencing 'captured_error'.
152+ del captured_error , self
138153
139154 def send (self , it ):
140155 self ._set_unwrapped ()
Original file line number Diff line number Diff line change 1+ import sys
12import traceback
23
34import pytest
@@ -117,3 +118,22 @@ def raise_ValueError(x):
117118 frames = traceback .extract_tb (exc_info .value .__traceback__ )
118119 functions = [function for _ , _ , function , _ in frames ]
119120 assert functions [- 2 :] == ['unwrap' , 'raise_ValueError' ]
121+
122+
123+ def test_Error_unwrap_does_not_create_reference_cycles ():
124+ # See comment in Error.unwrap for why reference cycles are tricky
125+ exc = ValueError ()
126+ err = Error (exc )
127+ try :
128+ err .unwrap ()
129+ except ValueError :
130+ pass
131+ # Top frame in the traceback is the current test function; we don't care
132+ # about its references
133+ assert exc .__traceback__ .tb_frame is sys ._getframe ()
134+ # The next frame down is the 'unwrap' frame; we want to make sure it
135+ # doesn't reference the exception (or anything else for that matter, just
136+ # to be thorough)
137+ unwrap_frame = exc .__traceback__ .tb_next .tb_frame
138+ assert unwrap_frame .f_code .co_name == "unwrap"
139+ assert unwrap_frame .f_locals == {}
You can’t perform that action at this time.
0 commit comments