Skip to content

Commit a40d300

Browse files
committedSep 5, 2022
std: clarify semantics of SGX parker
1 parent 633d46d commit a40d300

File tree

1 file changed

+29
-15
lines changed

1 file changed

+29
-15
lines changed
 

‎library/std/src/sys/sgx/thread_parker.rs

+29-15
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
99
use crate::time::Duration;
1010
use 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

1617
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.
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

Comments
 (0)
Please sign in to comment.