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):
134
134
# Tracebacks show the 'raise' line below out of context, so let's give
135
135
# this variable a name that makes sense out of context.
136
136
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
138
153
139
154
def send (self , it ):
140
155
self ._set_unwrapped ()
Original file line number Diff line number Diff line change
1
+ import sys
1
2
import traceback
2
3
3
4
import pytest
@@ -117,3 +118,22 @@ def raise_ValueError(x):
117
118
frames = traceback .extract_tb (exc_info .value .__traceback__ )
118
119
functions = [function for _ , _ , function , _ in frames ]
119
120
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 it's 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