Skip to content

Commit 1a39364

Browse files
committed
- ensure that thread locals are destructed when a spawned thread panics
- ensure that loom doesn't double panic on deadlock - add a test to verify that thread locals are properly destructed on spawned threads
1 parent ced476b commit 1a39364

File tree

4 files changed

+102
-6
lines changed

4 files changed

+102
-6
lines changed

src/rt/execution.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,13 @@ impl Execution {
207207

208208
self.threads.set_active(next);
209209

210-
// There is no active thread. Unless all threads have terminated, the
211-
// test has deadlocked.
210+
// There is no active thread. Unless all threads have terminated
211+
// or the current thread is panicking, the test has deadlocked.
212212
if !self.threads.is_active() {
213213
let terminal = self.threads.iter().all(|(_, th)| th.is_terminated());
214214

215215
assert!(
216-
terminal,
216+
terminal || std::thread::panicking(),
217217
"deadlock; threads = {:?}",
218218
self.threads
219219
.iter()

src/rt/mod.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,27 @@ where
7373
trace!(thread = ?id, "spawn");
7474

7575
Scheduler::spawn(Box::new(move || {
76+
/// the given closure `f` may panic when executed.
77+
/// when this happens, we still want to ensure that
78+
/// thread locals are destructed. therefore, we set
79+
/// up a guard that is dropped as part of the unwind
80+
/// logic when `f` panics.
81+
struct PanicGuard;
82+
impl Drop for PanicGuard {
83+
fn drop(&mut self) {
84+
thread_done(false);
85+
}
86+
}
87+
88+
// set up the panic guard
89+
let panic_guard = PanicGuard;
90+
91+
// execute the closure, note that `f()` may panic!
7692
f();
77-
thread_done(false);
93+
94+
// if `f()` didn't panic, then we terminate the
95+
// spawned thread by dropping the guard ourselves.
96+
drop(panic_guard);
7897
}));
7998

8099
id
@@ -172,6 +191,21 @@ where
172191
}
173192

174193
pub fn thread_done(is_main_thread: bool) {
194+
let is_active = execution(|execution| execution.threads.is_active());
195+
if !is_active {
196+
// if the thread is not active and the current thread is panicking,
197+
// then this means that loom has detected a problem (e.g. a deadlock).
198+
// we don't want to throw a double panic here, because this would cause
199+
// the entire test to abort and this hides the error from the end user.
200+
// instead we ensure that the current thread is panicking already,
201+
// or we cause a panic if it's not yet panicking (which it otherwise
202+
// would anyway, on the call to `execution.threads.active_id()` below).
203+
let panicking = std::thread::panicking();
204+
trace!(?panicking, "thread_done: no active thread");
205+
assert!(panicking);
206+
return;
207+
}
208+
175209
let locals = execution(|execution| {
176210
let thread = execution.threads.active_id();
177211

tests/deadlock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use loom::thread;
66
use std::rc::Rc;
77

88
#[test]
9-
#[should_panic]
9+
#[should_panic(expected = "deadlock; threads =")]
1010
fn two_mutexes_deadlock() {
1111
loom::model(|| {
1212
let a = Rc::new(Mutex::new(1));

tests/thread_local.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,77 @@ fn lazy_static_panic() {
162162

163163
impl Drop for Bar {
164164
fn drop(&mut self) {
165+
assert!(BAR.try_with(|_| ()).is_err());
165166
let _ = &*ID;
166167
}
167168
}
168169

169170
loom::model(|| {
170171
// initialize the TLS destructor:
171172
BAR.with(|_| ());
172-
println!("about to panic");
173173
// intentionally panic:
174174
panic!("loom should not panic inside another panic");
175175
});
176176
}
177+
178+
/// Test that thread locals are properly destructed
179+
/// when a spawned thread panics, without causing
180+
/// a double panic.
181+
#[test]
182+
#[should_panic(expected = "loom should not panic inside another panic")]
183+
fn access_on_drop_during_panic_in_spawned_thread() {
184+
use loom::{cell::Cell, thread};
185+
use std::{
186+
panic::catch_unwind,
187+
sync::{Mutex, MutexGuard, PoisonError},
188+
};
189+
190+
struct DropCounter {
191+
instantiated: usize,
192+
dropped: usize,
193+
}
194+
195+
static DROPPED: Mutex<DropCounter> = Mutex::new(DropCounter {
196+
instantiated: 0,
197+
dropped: 0,
198+
});
199+
200+
loom::thread_local! {
201+
static BAR: Cell<Bar> = Cell::new(Bar({
202+
let mut guard = DROPPED.lock().unwrap();
203+
guard.instantiated += 1;
204+
guard
205+
}));
206+
}
207+
208+
struct Bar(MutexGuard<'static, DropCounter>);
209+
impl Drop for Bar {
210+
fn drop(&mut self) {
211+
assert!(BAR.try_with(|_| ()).is_err());
212+
self.0.dropped += 1;
213+
}
214+
}
215+
216+
let result = catch_unwind(|| {
217+
loom::model(|| {
218+
thread::spawn(|| {
219+
// initialize the TLS destructor and panic:
220+
BAR.with(|_| {
221+
BAR.with(|_| {
222+
panic!();
223+
});
224+
});
225+
})
226+
.join()
227+
.unwrap();
228+
});
229+
});
230+
231+
let guard = DROPPED.lock().unwrap_or_else(PoisonError::into_inner);
232+
assert_eq!(guard.instantiated, 1);
233+
assert_eq!(guard.dropped, 1);
234+
235+
// propagate the panic from the spawned thread
236+
// to the main thread.
237+
result.expect("loom should not panic inside another panic");
238+
}

0 commit comments

Comments
 (0)