@@ -71,7 +71,116 @@ fn check_blocking() {
71
71
thread:: yield_now ( ) ;
72
72
}
73
73
74
+ // This test tests that TLS destructors have run before the thread joins. The
75
+ // test has no false positives (meaning: if the test fails, there's actually
76
+ // an ordering problem). It may have false negatives, where the test passes but
77
+ // join is not guaranteed to be after the TLS destructors. However, false
78
+ // negatives should be exceedingly rare due to judicious use of
79
+ // thread::yield_now and running the test several times.
80
+ fn join_orders_after_tls_destructors ( ) {
81
+ use std:: sync:: atomic:: { AtomicU8 , Ordering } ;
82
+
83
+ // We emulate a synchronous MPSC rendezvous channel using only atomics and
84
+ // thread::yield_now. We can't use std::mpsc as the implementation itself
85
+ // may rely on thread locals.
86
+ //
87
+ // The basic state machine for an SPSC rendezvous channel is:
88
+ // FRESH -> THREAD1_WAITING -> MAIN_THREAD_RENDEZVOUS
89
+ // where the first transition is done by the “receiving” thread and the 2nd
90
+ // transition is done by the “sending” thread.
91
+ //
92
+ // We add an additional state `THREAD2_LAUNCHED` between `FRESH` and
93
+ // `THREAD1_WAITING` to block until all threads are actually running.
94
+ //
95
+ // A thread that joins on the “receiving” thread completion should never
96
+ // observe the channel in the `THREAD1_WAITING` state. If this does occur,
97
+ // we switch to the “poison” state `THREAD2_JOINED` and panic all around.
98
+ // (This is equivalent to “sending” from an alternate producer thread.)
99
+ const FRESH : u8 = 0 ;
100
+ const THREAD2_LAUNCHED : u8 = 1 ;
101
+ const THREAD1_WAITING : u8 = 2 ;
102
+ const MAIN_THREAD_RENDEZVOUS : u8 = 3 ;
103
+ const THREAD2_JOINED : u8 = 4 ;
104
+ static SYNC_STATE : AtomicU8 = AtomicU8 :: new ( FRESH ) ;
105
+
106
+ for _ in 0 ..10 {
107
+ SYNC_STATE . store ( FRESH , Ordering :: SeqCst ) ;
108
+
109
+ let jh = thread:: Builder :: new ( )
110
+ . name ( "thread1" . into ( ) )
111
+ . spawn ( move || {
112
+ struct TlDrop ;
113
+
114
+ impl Drop for TlDrop {
115
+ fn drop ( & mut self ) {
116
+ let mut sync_state = SYNC_STATE . swap ( THREAD1_WAITING , Ordering :: SeqCst ) ;
117
+ loop {
118
+ match sync_state {
119
+ THREAD2_LAUNCHED | THREAD1_WAITING => thread:: yield_now ( ) ,
120
+ MAIN_THREAD_RENDEZVOUS => break ,
121
+ THREAD2_JOINED => panic ! (
122
+ "Thread 1 still running after thread 2 joined on thread 1"
123
+ ) ,
124
+ v => unreachable ! ( "sync state: {}" , v) ,
125
+ }
126
+ sync_state = SYNC_STATE . load ( Ordering :: SeqCst ) ;
127
+ }
128
+ }
129
+ }
130
+
131
+ thread_local ! {
132
+ static TL_DROP : TlDrop = TlDrop ;
133
+ }
134
+
135
+ TL_DROP . with ( |_| { } ) ;
136
+
137
+ loop {
138
+ match SYNC_STATE . load ( Ordering :: SeqCst ) {
139
+ FRESH => thread:: yield_now ( ) ,
140
+ THREAD2_LAUNCHED => break ,
141
+ v => unreachable ! ( "sync state: {}" , v) ,
142
+ }
143
+ }
144
+ } )
145
+ . unwrap ( ) ;
146
+
147
+ let jh2 = thread:: Builder :: new ( )
148
+ . name ( "thread2" . into ( ) )
149
+ . spawn ( move || {
150
+ assert_eq ! ( SYNC_STATE . swap( THREAD2_LAUNCHED , Ordering :: SeqCst ) , FRESH ) ;
151
+ jh. join ( ) . unwrap ( ) ;
152
+ match SYNC_STATE . swap ( THREAD2_JOINED , Ordering :: SeqCst ) {
153
+ MAIN_THREAD_RENDEZVOUS => return ,
154
+ THREAD2_LAUNCHED | THREAD1_WAITING => {
155
+ panic ! ( "Thread 2 running after thread 1 join before main thread rendezvous" )
156
+ }
157
+ v => unreachable ! ( "sync state: {:?}" , v) ,
158
+ }
159
+ } )
160
+ . unwrap ( ) ;
161
+
162
+ loop {
163
+ match SYNC_STATE . compare_exchange (
164
+ THREAD1_WAITING ,
165
+ MAIN_THREAD_RENDEZVOUS ,
166
+ Ordering :: SeqCst ,
167
+ Ordering :: SeqCst ,
168
+ ) {
169
+ Ok ( _) => break ,
170
+ Err ( FRESH ) => thread:: yield_now ( ) ,
171
+ Err ( THREAD2_LAUNCHED ) => thread:: yield_now ( ) ,
172
+ Err ( THREAD2_JOINED ) => {
173
+ panic ! ( "Main thread rendezvous after thread 2 joined thread 1" )
174
+ }
175
+ v => unreachable ! ( "sync state: {:?}" , v) ,
176
+ }
177
+ }
178
+ jh2. join ( ) . unwrap ( ) ;
179
+ }
180
+ }
181
+
74
182
fn main ( ) {
75
183
check_destructors ( ) ;
76
184
check_blocking ( ) ;
185
+ join_orders_after_tls_destructors ( ) ;
77
186
}
0 commit comments