|
1 | 1 | use crate::cell::{Cell, UnsafeCell};
|
| 2 | +use crate::sync::atomic::{AtomicU8, Ordering}; |
2 | 3 | use crate::sync::mpsc::{channel, Sender};
|
3 | 4 | use crate::thread::{self, LocalKey};
|
4 | 5 | use crate::thread_local;
|
@@ -207,3 +208,110 @@ fn dtors_in_dtors_in_dtors_const_init() {
|
207 | 208 | });
|
208 | 209 | rx.recv().unwrap();
|
209 | 210 | }
|
| 211 | + |
| 212 | +// This test tests that TLS destructors have run before the thread joins. The |
| 213 | +// test has no false positives (meaning: if the test fails, there's actually |
| 214 | +// an ordering problem). It may have false negatives, where the test passes but |
| 215 | +// join is not guaranteed to be after the TLS destructors. However, false |
| 216 | +// negatives should be exceedingly rare due to judicious use of |
| 217 | +// thread::yield_now and running the test several times. |
| 218 | +#[test] |
| 219 | +fn join_orders_after_tls_destructors() { |
| 220 | + // We emulate a synchronous MPSC rendezvous channel using only atomics and |
| 221 | + // thread::yield_now. We can't use std::mpsc as the implementation itself |
| 222 | + // may rely on thread locals. |
| 223 | + // |
| 224 | + // The basic state machine for an SPSC rendezvous channel is: |
| 225 | + // FRESH -> THREAD1_WAITING -> MAIN_THREAD_RENDEZVOUS |
| 226 | + // where the first transition is done by the “receiving” thread and the 2nd |
| 227 | + // transition is done by the “sending” thread. |
| 228 | + // |
| 229 | + // We add an additional state `THREAD2_LAUNCHED` between `FRESH` and |
| 230 | + // `THREAD1_WAITING` to block until all threads are actually running. |
| 231 | + // |
| 232 | + // A thread that joins on the “receiving” thread completion should never |
| 233 | + // observe the channel in the `THREAD1_WAITING` state. If this does occur, |
| 234 | + // we switch to the “poison” state `THREAD2_JOINED` and panic all around. |
| 235 | + // (This is equivalent to “sending” from an alternate producer thread.) |
| 236 | + const FRESH: u8 = 0; |
| 237 | + const THREAD2_LAUNCHED: u8 = 1; |
| 238 | + const THREAD1_WAITING: u8 = 2; |
| 239 | + const MAIN_THREAD_RENDEZVOUS: u8 = 3; |
| 240 | + const THREAD2_JOINED: u8 = 4; |
| 241 | + static SYNC_STATE: AtomicU8 = AtomicU8::new(FRESH); |
| 242 | + |
| 243 | + for _ in 0..10 { |
| 244 | + SYNC_STATE.store(FRESH, Ordering::SeqCst); |
| 245 | + |
| 246 | + let jh = thread::Builder::new() |
| 247 | + .name("thread1".into()) |
| 248 | + .spawn(move || { |
| 249 | + struct TlDrop; |
| 250 | + |
| 251 | + impl Drop for TlDrop { |
| 252 | + fn drop(&mut self) { |
| 253 | + let mut sync_state = SYNC_STATE.swap(THREAD1_WAITING, Ordering::SeqCst); |
| 254 | + loop { |
| 255 | + match sync_state { |
| 256 | + THREAD2_LAUNCHED | THREAD1_WAITING => thread::yield_now(), |
| 257 | + MAIN_THREAD_RENDEZVOUS => break, |
| 258 | + THREAD2_JOINED => panic!( |
| 259 | + "Thread 1 still running after thread 2 joined on thread 1" |
| 260 | + ), |
| 261 | + v => unreachable!("sync state: {}", v), |
| 262 | + } |
| 263 | + sync_state = SYNC_STATE.load(Ordering::SeqCst); |
| 264 | + } |
| 265 | + } |
| 266 | + } |
| 267 | + |
| 268 | + thread_local! { |
| 269 | + static TL_DROP: TlDrop = TlDrop; |
| 270 | + } |
| 271 | + |
| 272 | + TL_DROP.with(|_| {}); |
| 273 | + |
| 274 | + loop { |
| 275 | + match SYNC_STATE.load(Ordering::SeqCst) { |
| 276 | + FRESH => thread::yield_now(), |
| 277 | + THREAD2_LAUNCHED => break, |
| 278 | + v => unreachable!("sync state: {}", v), |
| 279 | + } |
| 280 | + } |
| 281 | + }) |
| 282 | + .unwrap(); |
| 283 | + |
| 284 | + let jh2 = thread::Builder::new() |
| 285 | + .name("thread2".into()) |
| 286 | + .spawn(move || { |
| 287 | + assert_eq!(SYNC_STATE.swap(THREAD2_LAUNCHED, Ordering::SeqCst), FRESH); |
| 288 | + jh.join().unwrap(); |
| 289 | + match SYNC_STATE.swap(THREAD2_JOINED, Ordering::SeqCst) { |
| 290 | + MAIN_THREAD_RENDEZVOUS => return, |
| 291 | + THREAD2_LAUNCHED | THREAD1_WAITING => { |
| 292 | + panic!("Thread 2 running after thread 1 join before main thread rendezvous") |
| 293 | + } |
| 294 | + v => unreachable!("sync state: {:?}", v), |
| 295 | + } |
| 296 | + }) |
| 297 | + .unwrap(); |
| 298 | + |
| 299 | + loop { |
| 300 | + match SYNC_STATE.compare_exchange_weak( |
| 301 | + THREAD1_WAITING, |
| 302 | + MAIN_THREAD_RENDEZVOUS, |
| 303 | + Ordering::SeqCst, |
| 304 | + Ordering::SeqCst, |
| 305 | + ) { |
| 306 | + Ok(_) => break, |
| 307 | + Err(FRESH) => thread::yield_now(), |
| 308 | + Err(THREAD2_LAUNCHED) => thread::yield_now(), |
| 309 | + Err(THREAD2_JOINED) => { |
| 310 | + panic!("Main thread rendezvous after thread 2 joined thread 1") |
| 311 | + } |
| 312 | + v => unreachable!("sync state: {:?}", v), |
| 313 | + } |
| 314 | + } |
| 315 | + jh2.join().unwrap(); |
| 316 | + } |
| 317 | +} |
0 commit comments