@@ -50,6 +50,20 @@ pub type CurrentFrameSlot = Arc<ThreadSlot>;
5050thread_local ! {
5151 pub ( super ) static VM_STACK : RefCell <Vec <NonNull <VirtualMachine >>> = Vec :: with_capacity( 1 ) . into( ) ;
5252
53+ /// Thread state created through the GILState-style C API.
54+ ///
55+ /// This is separate from the current VM stack: it only means "attached now",
56+ /// while this owns the per-thread VM that may be detached and re-attached.
57+ /// Despite the historical CPython "GILState" name, this does not model a
58+ /// GIL; it stores the VM used by that compatibility API.
59+ ///
60+ /// The Box keeps the VM address stable while VM_STACK holds a raw pointer to it.
61+ /// This matters when release_current_thread() moves the owner out of TLS and
62+ /// drops it while the VM is still current, so object destructors can still find
63+ /// their VM.
64+ #[ cfg( feature = "threading" ) ]
65+ static GILSTATE_VM : RefCell <Option <Box <ThreadedVirtualMachine >>> = const { RefCell :: new( None ) } ;
66+
5367 pub ( crate ) static COROUTINE_ORIGIN_TRACKING_DEPTH : Cell <u32 > = const { Cell :: new( 0 ) } ;
5468
5569 /// Current thread's slot for sys._current_frames() and sys._current_exceptions()
@@ -66,42 +80,114 @@ thread_local! {
6680
6781}
6882
69- scoped_tls:: scoped_thread_local!( pub static VM_CURRENT : VirtualMachine ) ;
83+ #[ must_use]
84+ pub fn current_vm_is_set ( ) -> bool {
85+ VM_STACK . with ( |vms| !vms. borrow ( ) . is_empty ( ) )
86+ }
7087
7188pub fn with_current_vm < R > ( f : impl FnOnce ( & VirtualMachine ) -> R ) -> R {
72- if !VM_CURRENT . is_set ( ) {
73- panic ! ( "call with_current_vm() but VM_CURRENT is null" ) ;
74- }
75- VM_CURRENT . with ( f)
89+ VM_STACK . with ( |vms| {
90+ let vm = vms
91+ . borrow ( )
92+ . last ( )
93+ . copied ( )
94+ . expect ( "call with_current_vm() but no current VM is attached" ) ;
95+ // SAFETY: entries in VM_STACK either borrow a VM for the dynamic
96+ // scope of a set_current_vm()/enter_vm() call or point at GILSTATE_VM.
97+ f ( unsafe { vm. as_ref ( ) } )
98+ } )
7699}
77100
78- pub fn enter_vm < R > ( vm : & VirtualMachine , f : impl FnOnce ( ) -> R ) -> R {
101+ fn set_current_vm < R > ( vm : & VirtualMachine , f : impl FnOnce ( ) -> R ) -> R {
79102 VM_STACK . with ( |vms| {
80- // Outermost enter_vm: transition DETACHED → ATTACHED
81- #[ cfg( all( unix, feature = "threading" ) ) ]
82- let was_outermost = vms. borrow ( ) . is_empty ( ) ;
83-
84103 vms. borrow_mut ( ) . push ( vm. into ( ) ) ;
104+ scopeguard:: defer! {
105+ vms. borrow_mut( ) . pop( ) ;
106+ }
107+ f ( )
108+ } )
109+ }
85110
86- // Initialize thread slot for this thread if not already done
87- #[ cfg( feature = "threading" ) ]
88- init_thread_slot_if_needed ( vm) ;
111+ pub fn enter_vm < R > ( vm : & VirtualMachine , f : impl FnOnce ( ) -> R ) -> R {
112+ // Outermost enter_vm: transition DETACHED → ATTACHED
113+ #[ cfg( all( unix, feature = "threading" ) ) ]
114+ let was_outermost = !current_vm_is_set ( ) ;
89115
116+ // Initialize thread slot for this thread if not already done
117+ #[ cfg( feature = "threading" ) ]
118+ init_thread_slot_if_needed ( vm) ;
119+
120+ #[ cfg( all( unix, feature = "threading" ) ) ]
121+ if was_outermost {
122+ attach_thread ( vm) ;
123+ }
124+
125+ scopeguard:: defer! {
126+ // Outermost exit: transition ATTACHED → DETACHED
90127 #[ cfg( all( unix, feature = "threading" ) ) ]
91128 if was_outermost {
92- attach_thread ( vm ) ;
129+ detach_thread ( ) ;
93130 }
131+ }
132+ set_current_vm ( vm, f)
133+ }
94134
95- scopeguard:: defer! {
96- // Outermost exit: transition ATTACHED → DETACHED
97- #[ cfg( all( unix, feature = "threading" ) ) ]
98- if vms. borrow( ) . len( ) == 1 {
99- detach_thread( ) ;
100- }
101- vms. borrow_mut( ) . pop( ) ;
102- }
103- VM_CURRENT . set ( vm, f)
104- } )
135+ #[ cfg( feature = "threading" ) ]
136+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
137+ pub enum CurrentVmAttachState {
138+ AlreadyAttached ,
139+ Attached ,
140+ }
141+
142+ /// Attach the current native thread to a RustPython VM until
143+ /// `release_current_thread()` is called.
144+ #[ cfg( feature = "threading" ) ]
145+ pub fn attach_current_thread (
146+ make_vm : impl FnOnce ( ) -> ThreadedVirtualMachine ,
147+ ) -> CurrentVmAttachState {
148+ if current_vm_is_set ( ) {
149+ return CurrentVmAttachState :: AlreadyAttached ;
150+ }
151+
152+ GILSTATE_VM . with ( |gilstate_vm| {
153+ let mut gilstate_vm = gilstate_vm. borrow_mut ( ) ;
154+ let threaded_vm = gilstate_vm. get_or_insert_with ( || Box :: new ( make_vm ( ) ) ) ;
155+ let vm = & threaded_vm. vm ;
156+
157+ vm. c_stack_soft_limit
158+ . set ( VirtualMachine :: calculate_c_stack_soft_limit ( ) ) ;
159+
160+ init_thread_slot_if_needed ( vm) ;
161+
162+ #[ cfg( unix) ]
163+ attach_thread ( vm) ;
164+
165+ VM_STACK . with ( |vms| {
166+ debug_assert ! ( vms. borrow( ) . is_empty( ) ) ;
167+ vms. borrow_mut ( ) . push ( vm. into ( ) ) ;
168+ } ) ;
169+ } ) ;
170+
171+ CurrentVmAttachState :: Attached
172+ }
173+
174+ #[ cfg( feature = "threading" ) ]
175+ pub fn release_current_thread ( state : CurrentVmAttachState ) {
176+ if state == CurrentVmAttachState :: AlreadyAttached {
177+ return ;
178+ }
179+
180+ let gilstate_vm = GILSTATE_VM . with ( |gilstate_vm| gilstate_vm. borrow_mut ( ) . take ( ) ) ;
181+ drop ( gilstate_vm) ;
182+
183+ VM_STACK . with ( |vms| {
184+ vms. borrow_mut ( )
185+ . pop ( )
186+ . expect ( "release_current_thread() called without an attached VM" ) ;
187+ } ) ;
188+
189+ #[ cfg( unix) ]
190+ detach_thread ( ) ;
105191}
106192
107193/// Initialize thread slot for current thread if not already initialized.
@@ -509,17 +595,20 @@ where
509595 obj. fast_isinstance ( vm. ctx . types . object_type )
510596 } ;
511597 VM_STACK . with ( |vms| {
512- let interp = match vms. borrow ( ) . iter ( ) . copied ( ) . exactly_one ( ) {
513- Ok ( x) => {
514- debug_assert ! ( vm_owns_obj( x) ) ;
515- x
598+ let interp = {
599+ let vms = vms. borrow ( ) ;
600+ match vms. iter ( ) . copied ( ) . exactly_one ( ) {
601+ Ok ( x) => {
602+ debug_assert ! ( vm_owns_obj( x) ) ;
603+ x
604+ }
605+ Err ( mut others) => others. find ( |x| vm_owns_obj ( * x) ) ?,
516606 }
517- Err ( mut others) => others. find ( |x| vm_owns_obj ( * x) ) ?,
518607 } ;
519608 // SAFETY: all references in VM_STACK should be valid, and should not be changed or moved
520609 // at least until this function returns and the stack unwinds to an enter_vm() call
521610 let vm = unsafe { interp. as_ref ( ) } ;
522- Some ( VM_CURRENT . set ( vm, || f ( vm) ) )
611+ Some ( set_current_vm ( vm, || f ( vm) ) )
523612 } )
524613}
525614
0 commit comments