Skip to content

Commit 4b78a54

Browse files
authored
Rollup merge of rust-lang#94644 - m-ou-se:scoped-threads-drop-soundness, r=joshtriplett
Fix soundness issue in scoped threads. This was discovered in rust-lang#94559 (comment) The `scope()` function returns when all threads are finished, but I accidentally considered a thread 'finished' before dropping their panic payload or ignored return value. So if a thread returned (or panics with) something that in its `Drop` implementation still uses borrowed stuff, it goes wrong. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=2a1f19ac4676cdabe43e24e536ff9358
2 parents 1f53134 + b97d875 commit 4b78a54

File tree

3 files changed

+52
-12
lines changed

3 files changed

+52
-12
lines changed

library/std/src/lib.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,11 @@ extern crate std as realstd;
364364
#[macro_use]
365365
mod macros;
366366

367+
// The runtime entry point and a few unstable public functions used by the
368+
// compiler
369+
#[macro_use]
370+
pub mod rt;
371+
367372
// The Rust prelude
368373
pub mod prelude;
369374

@@ -548,11 +553,6 @@ pub mod arch {
548553
#[stable(feature = "simd_x86", since = "1.27.0")]
549554
pub use std_detect::is_x86_feature_detected;
550555

551-
// The runtime entry point and a few unstable public functions used by the
552-
// compiler
553-
#[macro_use]
554-
pub mod rt;
555-
556556
// Platform-abstraction modules
557557
mod sys;
558558
mod sys_common;

library/std/src/thread/mod.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -1287,12 +1287,31 @@ unsafe impl<'scope, T: Sync> Sync for Packet<'scope, T> {}
12871287

12881288
impl<'scope, T> Drop for Packet<'scope, T> {
12891289
fn drop(&mut self) {
1290+
// If this packet was for a thread that ran in a scope, the thread
1291+
// panicked, and nobody consumed the panic payload, we make sure
1292+
// the scope function will panic.
1293+
let unhandled_panic = matches!(self.result.get_mut(), Some(Err(_)));
1294+
// Drop the result without causing unwinding.
1295+
// This is only relevant for threads that aren't join()ed, as
1296+
// join() will take the `result` and set it to None, such that
1297+
// there is nothing left to drop here.
1298+
// If this panics, we should handle that, because we're outside the
1299+
// outermost `catch_unwind` of our thread.
1300+
// We just abort in that case, since there's nothing else we can do.
1301+
// (And even if we tried to handle it somehow, we'd also need to handle
1302+
// the case where the panic payload we get out of it also panics on
1303+
// drop, and so on. See issue #86027.)
1304+
if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| {
1305+
*self.result.get_mut() = None;
1306+
})) {
1307+
rtabort!("thread result panicked on drop");
1308+
}
12901309
// Book-keeping so the scope knows when it's done.
12911310
if let Some(scope) = self.scope {
1292-
// If this packet was for a thread that ran in a scope, the thread
1293-
// panicked, and nobody consumed the panic payload, we make sure
1294-
// the scope function will panic.
1295-
let unhandled_panic = matches!(self.result.get_mut(), Some(Err(_)));
1311+
// Now that there will be no more user code running on this thread
1312+
// that can use 'scope, mark the thread as 'finished'.
1313+
// It's important we only do this after the `result` has been dropped,
1314+
// since dropping it might still use things it borrowed from 'scope.
12961315
scope.decrement_num_running_threads(unhandled_panic);
12971316
}
12981317
}

library/std/src/thread/tests.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ use crate::mem;
44
use crate::panic::panic_any;
55
use crate::result;
66
use crate::sync::{
7+
atomic::{AtomicBool, Ordering},
78
mpsc::{channel, Sender},
89
Arc, Barrier,
910
};
10-
use crate::thread::{self, ThreadId};
11+
use crate::thread::{self, Scope, ThreadId};
1112
use crate::time::Duration;
1213
use crate::time::Instant;
1314

@@ -293,5 +294,25 @@ fn test_thread_id_not_equal() {
293294
assert!(thread::current().id() != spawned_id);
294295
}
295296

296-
// NOTE: the corresponding test for stderr is in ui/thread-stderr, due
297-
// to the test harness apparently interfering with stderr configuration.
297+
#[test]
298+
fn test_scoped_threads_drop_result_before_join() {
299+
let actually_finished = &AtomicBool::new(false);
300+
struct X<'scope, 'env>(&'scope Scope<'scope, 'env>, &'env AtomicBool);
301+
impl Drop for X<'_, '_> {
302+
fn drop(&mut self) {
303+
thread::sleep(Duration::from_millis(20));
304+
let actually_finished = self.1;
305+
self.0.spawn(move || {
306+
thread::sleep(Duration::from_millis(20));
307+
actually_finished.store(true, Ordering::Relaxed);
308+
});
309+
}
310+
}
311+
thread::scope(|s| {
312+
s.spawn(move || {
313+
thread::sleep(Duration::from_millis(20));
314+
X(s, actually_finished)
315+
});
316+
});
317+
assert!(actually_finished.load(Ordering::Relaxed));
318+
}

0 commit comments

Comments
 (0)