@@ -9,11 +9,17 @@ use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
9
9
use crate :: time:: Duration ;
10
10
use fortanix_sgx_abi:: { EV_UNPARK , WAIT_INDEFINITE } ;
11
11
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 ) ;
15
16
16
17
pub 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.
17
23
state : AtomicPtr < u8 > ,
18
24
}
19
25
@@ -30,23 +36,28 @@ impl Parker {
30
36
31
37
// This implementation doesn't require `unsafe` and `Pin`, but other implementations do.
32
38
pub unsafe fn park ( self : Pin < & Self > ) {
33
- let tcs = thread:: current ( ) . as_ptr ( ) ;
34
-
35
39
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 ( ) {
39
46
let event = usercalls:: wait ( EV_UNPARK , WAIT_INDEFINITE ) . unwrap ( ) ;
40
47
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 ;
44
55
}
45
56
}
46
57
}
47
58
48
59
// 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.
50
61
self . state . store ( EMPTY , Relaxed ) ;
51
62
}
52
63
@@ -56,7 +67,7 @@ impl Parker {
56
67
let tcs = thread:: current ( ) . as_ptr ( ) ;
57
68
58
69
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 ( ) {
60
71
match usercalls:: wait ( EV_UNPARK , timeout) {
61
72
Ok ( event) => assert ! ( event & EV_UNPARK == EV_UNPARK ) ,
62
73
Err ( e) => {
@@ -85,8 +96,11 @@ impl Parker {
85
96
if !matches ! ( state, EMPTY | NOTIFIED ) {
86
97
// There is a thread waiting, wake it up.
87
98
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.
90
104
let _ = usercalls:: send ( EV_UNPARK , Some ( tcs) ) ;
91
105
}
92
106
}
0 commit comments