Skip to content

Commit 3807110

Browse files
committed
Avoid double panic when queued_spawn is dropped
1 parent 101c335 commit 3807110

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

src/rt/scheduler.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -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

tests/arc.rs

+23
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,26 @@ fn try_unwrap_multithreaded() {
128128
let _ = Arc::try_unwrap(num).unwrap();
129129
});
130130
}
131+
132+
/// Test that there is no double panic
133+
/// when a model with an Arc in an unspawned thread panics.
134+
#[test]
135+
#[should_panic(expected = "loom should not panic inside another panic")]
136+
fn access_on_drop_during_panic_in_unspawned_thread() {
137+
use loom::sync::Arc;
138+
use std::panic::catch_unwind;
139+
140+
let result = catch_unwind(|| {
141+
loom::model(move || {
142+
let arc = Arc::new(());
143+
thread::spawn(move || {
144+
let _arc = arc;
145+
});
146+
panic!();
147+
});
148+
});
149+
150+
// propagate the panic from the spawned thread
151+
// to the main thread.
152+
result.expect("loom should not panic inside another panic");
153+
}

0 commit comments

Comments
 (0)