@@ -9,11 +9,17 @@ use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
99use crate :: time:: Duration ;
1010use fortanix_sgx_abi:: { EV_UNPARK , WAIT_INDEFINITE } ;
1111
12- const EMPTY : * mut u8 = ptr:: invalid_mut ( 0 ) ;
13- /// The TCS structure must be page-aligned, so this cannot be a valid pointer
14- const NOTIFIED : * mut u8 = ptr:: invalid_mut ( 1 ) ;
12+ // The TCS structure must be page-aligned (this is checked by EENTER), so these cannot
13+ // be valid pointers
14+ const EMPTY : * mut u8 = ptr:: invalid_mut ( 1 ) ;
15+ const NOTIFIED : * mut u8 = ptr:: invalid_mut ( 2 ) ;
1516
1617pub struct Parker {
18+ /// The park state. One of EMPTY, NOTIFIED or a TCS address.
19+ /// A state change to NOTIFIED must be done with release ordering
20+ /// and be observed with acquire ordering so that operations after
21+ /// `thread::park` returns will not occur before the unpark message
22+ /// was sent.
1723 state : AtomicPtr < u8 > ,
1824}
1925
@@ -30,23 +36,28 @@ impl Parker {
3036
3137 // This implementation doesn't require `unsafe` and `Pin`, but other implementations do.
3238 pub unsafe fn park ( self : Pin < & Self > ) {
33- let tcs = thread:: current ( ) . as_ptr ( ) ;
34-
3539 if self . state . load ( Acquire ) != NOTIFIED {
36- if self . state . compare_exchange ( EMPTY , tcs, Acquire , Acquire ) . is_ok ( ) {
37- // Loop to guard against spurious wakeups.
38- loop {
40+ let mut prev = EMPTY ;
41+ loop {
42+ // Guard against changing TCS addresses by always setting the state to
43+ // the current value.
44+ let tcs = thread:: current ( ) . as_ptr ( ) ;
45+ if self . state . compare_exchange ( prev, tcs, Relaxed , Acquire ) . is_ok ( ) {
3946 let event = usercalls:: wait ( EV_UNPARK , WAIT_INDEFINITE ) . unwrap ( ) ;
4047 assert ! ( event & EV_UNPARK == EV_UNPARK ) ;
41- if self . state . load ( Acquire ) == NOTIFIED {
42- break ;
43- }
48+ prev = tcs;
49+ } else {
50+ // The state was definitely changed by another thread at this point.
51+ // The only time this occurs is when the state is changed to NOTIFIED.
52+ // We observed this change with acquire ordering, so we can simply
53+ // change the state to EMPTY with a relaxed store.
54+ break ;
4455 }
4556 }
4657 }
4758
4859 // At this point, the token was definately read with acquire ordering,
49- // so this can be a store.
60+ // so this can be a relaxed store.
5061 self . state . store ( EMPTY , Relaxed ) ;
5162 }
5263
@@ -56,7 +67,7 @@ impl Parker {
5667 let tcs = thread:: current ( ) . as_ptr ( ) ;
5768
5869 if self . state . load ( Acquire ) != NOTIFIED {
59- if self . state . compare_exchange ( EMPTY , tcs, Acquire , Acquire ) . is_ok ( ) {
70+ if self . state . compare_exchange ( EMPTY , tcs, Relaxed , Acquire ) . is_ok ( ) {
6071 match usercalls:: wait ( EV_UNPARK , timeout) {
6172 Ok ( event) => assert ! ( event & EV_UNPARK == EV_UNPARK ) ,
6273 Err ( e) => {
@@ -85,8 +96,11 @@ impl Parker {
8596 if !matches ! ( state, EMPTY | NOTIFIED ) {
8697 // There is a thread waiting, wake it up.
8798 let tcs = NonNull :: new ( state) . unwrap ( ) ;
88- // This will fail if the thread has already terminated by the time the signal is send,
89- // but that is OK.
99+ // This will fail if the thread has already terminated or its TCS is destroyed
100+ // by the time the signal is sent, but that is fine. If another thread receives
101+ // the same TCS, it will receive this notification as a spurious wakeup, but
102+ // all users of `wait` should and (internally) do guard against those where
103+ // necessary.
90104 let _ = usercalls:: send ( EV_UNPARK , Some ( tcs) ) ;
91105 }
92106 }
0 commit comments