@@ -127,7 +127,41 @@ impl Scheduler {
127127 panic ! ( "cannot access Loom execution state from outside a Loom model. \
128128 are you accessing a Loom synchronization primitive from outside a Loom test (a call to `model` or `check`)?")
129129 }
130- STATE . with ( |state| f ( & mut state. borrow_mut ( ) ) )
130+ STATE . with ( |state| {
131+ // When unwinding after a panic, `STATE` is unset before `Scheduler` is dropped.
132+ // However, `Scheduler::queued_spawn` could hold loom objects which would try to use
133+ // `STATE` when they are dropped. Because of that, we need to clear `queued_spawn`
134+ // while STATE is still set. Since `STATE` has an exclusive reference (&mut) to
135+ // `Scheduler::queued_spawn`, we also need to use `STATE` ourselves for accessing them,
136+ // but drop our `RefMut` before the dropping of `queued_spawn` happens.
137+ //
138+ // To implement this, we exploit the fact that the struct fields of `DropGuard`
139+ // are dropped in declaration order, and move `queued_spawn`in `DropGuard::drop`
140+ // to the second struct field of `DropGuard` (replacing it with an empty `VecDeque`).
141+ // Then, the destructor first drops the `RefMut` (thereby allowing `STATE` to be
142+ // borrowed again), and then the former `queued_spawn` value (possibly accessing `STATE`).
143+ struct DropGuard < ' a , ' b > (
144+ std:: cell:: RefMut < ' a , State < ' b > > ,
145+ VecDeque < Box < dyn FnOnce ( ) > > ,
146+ ) ;
147+ impl < ' a , ' b > Drop for DropGuard < ' a , ' b > {
148+ fn drop ( & mut self ) {
149+ if std:: thread:: panicking ( ) {
150+ self . 1 = std:: mem:: take ( self . 0 . queued_spawn ) ;
151+ }
152+ }
153+ }
154+ impl < ' a , ' b > DropGuard < ' a , ' b > {
155+ fn run < F , R > ( & mut self , f : F ) -> R
156+ where
157+ F : FnOnce ( & mut State < ' b > ) -> R ,
158+ {
159+ f ( & mut self . 0 )
160+ }
161+ }
162+ let mut guard = DropGuard ( state. borrow_mut ( ) , Default :: default ( ) ) ;
163+ guard. run ( f)
164+ } )
131165 }
132166}
133167
0 commit comments