@@ -30,7 +30,7 @@ impl ExecutionResult {
3030
3131#[ derive( Debug ) ]
3232pub struct Coro {
33- frame : FrameObjectRef ,
33+ frame : PyMutex < FrameObjectRef > ,
3434 pub closed : AtomicCell < bool > , // TODO: https://github.com/RustPython/RustPython/pull/3183#discussion_r720560652
3535 running : AtomicCell < bool > ,
3636 // code
@@ -42,7 +42,7 @@ pub struct Coro {
4242
4343unsafe impl Traverse for Coro {
4444 fn traverse ( & self , tracer_fn : & mut TraverseFn < ' _ > ) {
45- self . frame . traverse ( tracer_fn) ;
45+ self . frame . lock ( ) . traverse ( tracer_fn) ;
4646 self . name . traverse ( tracer_fn) ;
4747 self . qualname . traverse ( tracer_fn) ;
4848 if let Some ( exc) = self . exception . deref ( ) {
@@ -80,7 +80,7 @@ fn gen_name(jen: &PyObject, vm: &VirtualMachine) -> &'static str {
8080impl Coro {
8181 pub fn new ( frame : FrameObjectRef , name : PyStrRef , qualname : PyStrRef ) -> Self {
8282 Self {
83- frame,
83+ frame : PyMutex :: new ( frame ) ,
8484 closed : AtomicCell :: new ( false ) ,
8585 running : AtomicCell :: new ( false ) ,
8686 exception : PyAtomicRef :: from ( None ) ,
@@ -89,40 +89,48 @@ impl Coro {
8989 }
9090 }
9191
92- /// Free the finished frame's locals and stack, unless a frame object has
93- /// escaped (e.g. through an `f_locals` proxy or `sys._getframe`). An
94- /// escaped frame husk owns its heap-resident locals and must keep them
95- /// readable after the generator closes.
96- fn clear_frame_locals_on_close ( & self ) {
97- // Keep locals alive if a durable frame reference escaped (e.g. through
98- // an `f_locals` proxy or `sys._getframe`): that reference now owns the
99- // heap-resident locals and must keep them readable after close,
100- // matching `take_ownership`.
101- if !self . frame . has_escaped ( ) {
102- self . frame . clear_locals_and_stack ( ) ;
92+ /// `_PyFrame_ClearExceptCode`. Clear locals unless another reference
93+ /// still holds the frame object; then take_ownership instead.
94+ fn clear_except_code ( & self , jen : & PyObject , vm : & VirtualMachine ) {
95+ let unique = self . frame . lock ( ) . as_object ( ) . strong_count ( ) == 1 ;
96+ if unique {
97+ self . frame . lock ( ) . clear_locals_and_stack ( ) ;
98+ } else {
99+ self . take_ownership ( jen, vm) ;
103100 }
104101 }
105102
106- /// Mark the wrapper closed and hand the frame to FrameObject so
107- /// `frame.clear()` no longer treats it as a live generator frame .
108- fn mark_closed ( & self ) {
109- self . closed . store ( true ) ;
110- self . frame . iframe ( ) . owner . store (
103+ /// Move the live iframe onto the independently owned frame object and
104+ /// leave the generator holding only an empty husk (`take_ownership`) .
105+ fn take_ownership ( & self , jen : & PyObject , vm : & VirtualMachine ) {
106+ let current = self . frame . lock ( ) . clone ( ) ;
107+ current . iframe ( ) . owner . store (
111108 FrameOwner :: FrameObject as i8 ,
112109 core:: sync:: atomic:: Ordering :: Release ,
113110 ) ;
111+ current. clear_generator ( ) ;
112+ let husk = FrameObject :: husk_from ( & current, vm) ;
113+ husk. set_generator ( jen) ;
114+ let _old = {
115+ let mut guard = self . frame . lock ( ) ;
116+ core:: mem:: replace ( & mut * guard, husk)
117+ } ;
114118 }
115119
116120 /// Retire the generator if the frame it just ran came to an end. The claim
117121 /// is still held, so a thread waiting for it cannot resume a frame that has
118122 /// already finished.
119- fn maybe_close ( & self , res : & PyResult < ExecutionResult > , _claim : & RunningGuard < ' _ > ) {
123+ fn maybe_close (
124+ & self ,
125+ res : & PyResult < ExecutionResult > ,
126+ jen : & PyObject ,
127+ vm : & VirtualMachine ,
128+ _claim : & RunningGuard < ' _ > ,
129+ ) {
120130 match res {
121131 Ok ( ExecutionResult :: Return ( _) ) | Err ( _) => {
122- self . mark_closed ( ) ;
123- // Completed generators/coroutines should not keep their locals
124- // alive while the wrapper object itself remains referenced.
125- self . clear_frame_locals_on_close ( ) ;
132+ self . closed . store ( true ) ;
133+ self . clear_except_code ( jen, vm) ;
126134 }
127135 Ok ( ExecutionResult :: Yield ( _) ) => { }
128136 Ok ( ExecutionResult :: TailCall ) => unreachable ! ( "TailCall in generator/coroutine" ) ,
@@ -153,7 +161,8 @@ impl Coro {
153161 let gen_exc = unsafe { self . exception . swap ( None ) } ;
154162 let exception_ptr = & self . exception as * const PyAtomicRef < Option < PyBaseException > > ;
155163
156- vm. resume_gen_frame ( & self . frame , gen_exc, |f| {
164+ let frame = self . frame ( ) ;
165+ vm. resume_gen_frame ( & frame, gen_exc, |f| {
157166 let result = func ( f) ;
158167 // SAFETY: exclusive access guaranteed by the claim
159168 let _old = unsafe { ( * exception_ptr) . swap ( vm. current_exception ( ) ) } ;
@@ -173,8 +182,8 @@ impl Coro {
173182 if e. fast_isinstance ( vm. ctx . exceptions . stop_iteration ) {
174183 let err =
175184 vm. new_runtime_error ( format ! ( "{} raised StopIteration" , gen_name( jen, vm) ) ) ;
176- // PEP 479: chain __context__ as well as __cause__ to match
177- // CPython, which sets both to the original StopIteration.
185+ // PEP 479: chain __context__ as well as __cause__ to the
186+ // original StopIteration.
178187 err. set___context__ ( Some ( e. clone ( ) ) ) ;
179188 err. set___cause__ ( Some ( e) ) ;
180189 Err ( err)
@@ -201,13 +210,13 @@ impl Coro {
201210 if self . closed . load ( ) {
202211 return Self :: send_when_closed ( jen, vm) ;
203212 }
204- let value = if self . frame . lasti ( ) > 0 {
213+ let value = if self . frame ( ) . lasti ( ) > 0 {
205214 Some ( vm. ctx . none ( ) )
206215 } else {
207216 None
208217 } ;
209218 let result = self . run_claimed ( & claim, vm, |f| f. resume ( value, vm) ) ;
210- self . maybe_close ( & result, & claim) ;
219+ self . maybe_close ( & result, jen , vm , & claim) ;
211220 drop ( claim) ;
212221 self . finalize_send_result ( result, jen, vm)
213222 }
@@ -248,7 +257,7 @@ impl Coro {
248257 if self . closed . load ( ) {
249258 return Self :: send_when_closed ( jen, vm) ;
250259 }
251- let value = if self . frame . lasti ( ) > 0 {
260+ let value = if self . frame ( ) . lasti ( ) > 0 {
252261 Some ( value)
253262 } else if !vm. is_none ( & value) {
254263 return Err ( vm. new_type_error ( format ! (
@@ -259,7 +268,7 @@ impl Coro {
259268 None
260269 } ;
261270 let result = self . run_claimed ( & claim, vm, |f| f. resume ( value, vm) ) ;
262- self . maybe_close ( & result, & claim) ;
271+ self . maybe_close ( & result, jen , vm , & claim) ;
263272 drop ( claim) ;
264273 self . finalize_send_result ( result, jen, vm)
265274 }
@@ -294,7 +303,7 @@ impl Coro {
294303 return Self :: throw_when_closed ( jen, exc_type, exc_val, exc_tb, vm) ;
295304 }
296305 let result = self . run_claimed ( & claim, vm, |f| f. gen_throw ( vm, exc_type, exc_val, exc_tb) ) ;
297- self . maybe_close ( & result, & claim) ;
306+ self . maybe_close ( & result, jen , vm , & claim) ;
298307 drop ( claim) ;
299308 self . finalize_send_result ( result, jen, vm)
300309 }
@@ -308,11 +317,10 @@ impl Coro {
308317 if self . closed . load ( ) {
309318 return Ok ( vm. ctx . none ( ) ) ;
310319 }
311- // If generator hasn't started (FRAME_CREATED), mark as closed and
312- // drop frame locals so argument objects can be collected.
313- if self . frame . lasti ( ) == 0 {
314- self . mark_closed ( ) ;
315- self . clear_frame_locals_on_close ( ) ;
320+ // FRAME_CREATED: mark finished and clear the iframe.
321+ if self . frame ( ) . lasti ( ) == 0 {
322+ self . closed . store ( true ) ;
323+ self . clear_except_code ( jen, vm) ;
316324 return Ok ( vm. ctx . none ( ) ) ;
317325 }
318326 let result = self . run_claimed ( & claim, vm, |f| {
@@ -323,23 +331,25 @@ impl Coro {
323331 vm. ctx . none ( ) ,
324332 )
325333 } ) ;
326- self . mark_closed ( ) ;
327- // Release frame locals and stack to free references held by the
328- // closed generator, matching gen_send_ex2 with close_on_completion.
329- self . clear_frame_locals_on_close ( ) ;
330334 drop ( claim) ;
331335 match result {
332336 Ok ( ExecutionResult :: Yield ( _) ) => {
333337 Err ( vm. new_runtime_error ( format ! ( "{} ignored GeneratorExit" , gen_name( jen, vm) ) ) )
334338 }
335- Err ( e) if !is_gen_exit ( & e, vm) => Err ( e) ,
336- Ok ( ExecutionResult :: Return ( value) ) => Ok ( value) ,
337- _ => Ok ( vm. ctx . none ( ) ) ,
339+ other => {
340+ self . closed . store ( true ) ;
341+ self . clear_except_code ( jen, vm) ;
342+ match other {
343+ Err ( e) if !is_gen_exit ( & e, vm) => Err ( e) ,
344+ Ok ( ExecutionResult :: Return ( value) ) => Ok ( value) ,
345+ _ => Ok ( vm. ctx . none ( ) ) ,
346+ }
347+ }
338348 }
339349 }
340350
341351 pub fn suspended ( & self ) -> bool {
342- !self . closed . load ( ) && !self . running . load ( ) && self . frame . lasti ( ) > 0
352+ !self . closed . load ( ) && !self . running . load ( ) && self . frame ( ) . lasti ( ) > 0
343353 }
344354
345355 pub fn running ( & self ) -> bool {
@@ -351,7 +361,7 @@ impl Coro {
351361 }
352362
353363 pub fn frame ( & self ) -> FrameObjectRef {
354- self . frame . clone ( )
364+ self . frame . lock ( ) . clone ( )
355365 }
356366
357367 pub fn name ( & self ) -> PyStrRef {
0 commit comments