11use crate :: frame:: Frame ;
22#[ cfg( feature = "threading" ) ]
3- use crate :: frame:: FrameRef ;
3+ use crate :: { builtins :: PyBaseExceptionRef , frame:: FrameRef } ;
44use crate :: { AsObject , PyObject , VirtualMachine } ;
55#[ cfg( feature = "threading" ) ]
66use alloc:: sync:: Arc ;
@@ -18,6 +18,11 @@ use std::thread_local;
1818#[ cfg( feature = "threading" ) ]
1919pub type CurrentFrameSlot = Arc < parking_lot:: Mutex < Vec < FrameRef > > > ;
2020
21+ /// Type for current exception slot - shared between threads for sys._current_exceptions()
22+ /// Each thread updates its own slot via thread-local reference (no global lock on hot path).
23+ #[ cfg( feature = "threading" ) ]
24+ pub type CurrentExceptionSlot = Arc < crate :: common:: lock:: PyMutex < Option < PyBaseExceptionRef > > > ;
25+
2126thread_local ! {
2227 pub ( super ) static VM_STACK : RefCell <Vec <NonNull <VirtualMachine >>> = Vec :: with_capacity( 1 ) . into( ) ;
2328
@@ -27,6 +32,10 @@ thread_local! {
2732 #[ cfg( feature = "threading" ) ]
2833 static CURRENT_FRAME_SLOT : RefCell <Option <CurrentFrameSlot >> = const { RefCell :: new( None ) } ;
2934
35+ /// Current thread's exception slot for sys._current_exceptions()
36+ #[ cfg( feature = "threading" ) ]
37+ static CURRENT_EXCEPTION_SLOT : RefCell <Option <CurrentExceptionSlot >> = const { RefCell :: new( None ) } ;
38+
3039 /// Current top frame for signal-safe traceback walking.
3140 /// Mirrors `PyThreadState.current_frame`. Read by faulthandler's signal
3241 /// handler to dump tracebacks without accessing RefCell or locks.
@@ -65,12 +74,20 @@ fn init_frame_slot_if_needed(vm: &VirtualMachine) {
6574 CURRENT_FRAME_SLOT . with ( |slot| {
6675 if slot. borrow ( ) . is_none ( ) {
6776 let thread_id = crate :: stdlib:: thread:: get_ident ( ) ;
68- let new_slot = Arc :: new ( parking_lot:: Mutex :: new ( Vec :: new ( ) ) ) ;
77+ let new_frame_slot = Arc :: new ( parking_lot:: Mutex :: new ( Vec :: new ( ) ) ) ;
78+ let new_exc_slot = Arc :: new ( crate :: common:: lock:: PyMutex :: new ( None ) ) ;
6979 vm. state
7080 . thread_frames
7181 . lock ( )
72- . insert ( thread_id, new_slot. clone ( ) ) ;
73- * slot. borrow_mut ( ) = Some ( new_slot) ;
82+ . insert ( thread_id, new_frame_slot. clone ( ) ) ;
83+ vm. state
84+ . thread_exceptions
85+ . lock ( )
86+ . insert ( thread_id, new_exc_slot. clone ( ) ) ;
87+ * slot. borrow_mut ( ) = Some ( new_frame_slot) ;
88+ CURRENT_EXCEPTION_SLOT . with ( |es| {
89+ * es. borrow_mut ( ) = Some ( new_exc_slot) ;
90+ } ) ;
7491 }
7592 } ) ;
7693}
@@ -109,14 +126,43 @@ pub fn get_current_frame() -> *const Frame {
109126 CURRENT_FRAME . with ( |c| c. load ( Ordering :: Relaxed ) as * const Frame )
110127}
111128
129+ /// Update the current thread's exception slot via thread-local (no global lock).
130+ /// Called from push_exception/pop_exception/set_exception.
131+ #[ cfg( feature = "threading" ) ]
132+ pub fn sync_thread_exception ( exc : Option < PyBaseExceptionRef > ) {
133+ CURRENT_EXCEPTION_SLOT . with ( |slot| {
134+ if let Some ( s) = slot. borrow ( ) . as_ref ( ) {
135+ * s. lock ( ) = exc;
136+ }
137+ } ) ;
138+ }
139+
140+ /// Collect all threads' current exceptions for sys._current_exceptions().
141+ /// Only acquires the global registry lock briefly to clone slot references,
142+ /// then reads each slot individually.
143+ #[ cfg( feature = "threading" ) ]
144+ pub fn get_all_current_exceptions (
145+ vm : & VirtualMachine ,
146+ ) -> Vec < ( u64 , Option < PyBaseExceptionRef > ) > {
147+ let registry = vm. state . thread_exceptions . lock ( ) ;
148+ registry
149+ . iter ( )
150+ . map ( |( id, slot) | ( * id, slot. lock ( ) . clone ( ) ) )
151+ . collect ( )
152+ }
153+
112154/// Cleanup frame tracking for the current thread. Called at thread exit.
113155#[ cfg( feature = "threading" ) ]
114156pub fn cleanup_current_thread_frames ( vm : & VirtualMachine ) {
115157 let thread_id = crate :: stdlib:: thread:: get_ident ( ) ;
116158 vm. state . thread_frames . lock ( ) . remove ( & thread_id) ;
159+ vm. state . thread_exceptions . lock ( ) . remove ( & thread_id) ;
117160 CURRENT_FRAME_SLOT . with ( |s| {
118161 * s. borrow_mut ( ) = None ;
119162 } ) ;
163+ CURRENT_EXCEPTION_SLOT . with ( |s| {
164+ * s. borrow_mut ( ) = None ;
165+ } ) ;
120166}
121167
122168/// Reinitialize frame slot after fork. Called in child process.
@@ -144,10 +190,25 @@ pub fn reinit_frame_slot_after_fork(vm: &VirtualMachine) {
144190 registry. insert ( current_ident, new_slot. clone ( ) ) ;
145191 drop ( registry) ;
146192
147- // Update thread-local to point to the new slot
193+ let new_exc_slot = Arc :: new ( crate :: common:: lock:: PyMutex :: new ( vm. topmost_exception ( ) ) ) ;
194+ let mut exc_registry = match vm. state . thread_exceptions . try_lock ( ) {
195+ Some ( guard) => guard,
196+ None => {
197+ unsafe { vm. state . thread_exceptions . force_unlock ( ) } ;
198+ vm. state . thread_exceptions . lock ( )
199+ }
200+ } ;
201+ exc_registry. clear ( ) ;
202+ exc_registry. insert ( current_ident, new_exc_slot. clone ( ) ) ;
203+ drop ( exc_registry) ;
204+
205+ // Update thread-local to point to the new slots
148206 CURRENT_FRAME_SLOT . with ( |s| {
149207 * s. borrow_mut ( ) = Some ( new_slot) ;
150208 } ) ;
209+ CURRENT_EXCEPTION_SLOT . with ( |s| {
210+ * s. borrow_mut ( ) = Some ( new_exc_slot) ;
211+ } ) ;
151212}
152213
153214pub fn with_vm < F , R > ( obj : & PyObject , f : F ) -> Option < R >
0 commit comments