Skip to content

Commit 118e8f7

Browse files
bunniexobs
authored andcommitted
xous: std: thread_parking: fix deadlocks
Fix a deadlock condition that can occur when a thread is awoken in between the point at which it checks its wake state and the point where it actually waits. This change will cause the waker to continuously send Notify messages until it actually wakes up the target thread. Signed-off-by: Sean Cross <sean@xobs.io>
1 parent 944dc21 commit 118e8f7

File tree

1 file changed

+40
-22
lines changed

1 file changed

+40
-22
lines changed

library/std/src/sys/pal/xous/thread_parking.rs

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,60 +29,78 @@ impl Parker {
2929
// Change NOTIFIED to EMPTY and EMPTY to PARKED.
3030
let state = self.state.fetch_sub(1, Acquire);
3131
if state == NOTIFIED {
32+
// The state has gone from NOTIFIED (1) to EMPTY (0)
3233
return;
3334
}
35+
// The state has gone from EMPTY (0) to PARKED (-1)
36+
assert!(state == EMPTY);
3437

35-
// The state was set to PARKED. Wait until the `unpark` wakes us up.
38+
// The state is now PARKED (-1). Wait until the `unpark` wakes us up.
3639
blocking_scalar(
3740
ticktimer_server(),
3841
TicktimerScalar::WaitForCondition(self.index(), 0).into(),
3942
)
4043
.expect("failed to send WaitForCondition command");
4144

42-
self.state.swap(EMPTY, Acquire);
45+
let state = self.state.swap(EMPTY, Acquire);
46+
assert!(state == NOTIFIED || state == PARKED);
4347
}
4448

4549
pub unsafe fn park_timeout(self: Pin<&Self>, timeout: Duration) {
4650
// Change NOTIFIED to EMPTY and EMPTY to PARKED.
4751
let state = self.state.fetch_sub(1, Acquire);
4852
if state == NOTIFIED {
53+
// The state has gone from NOTIFIED (1) to EMPTY (0)
4954
return;
5055
}
56+
// The state has gone from EMPTY (0) to PARKED (-1)
57+
assert!(state == EMPTY);
5158

5259
// A value of zero indicates an indefinite wait. Clamp the number of
5360
// milliseconds to the allowed range.
5461
let millis = usize::max(timeout.as_millis().try_into().unwrap_or(usize::MAX), 1);
5562

56-
let was_timeout = blocking_scalar(
63+
// The state is now PARKED (-1). Wait until the `unpark` wakes us up,
64+
// or things time out.
65+
let _was_timeout = blocking_scalar(
5766
ticktimer_server(),
5867
TicktimerScalar::WaitForCondition(self.index(), millis).into(),
5968
)
6069
.expect("failed to send WaitForCondition command")[0]
6170
!= 0;
6271

6372
let state = self.state.swap(EMPTY, Acquire);
64-
if was_timeout && state == NOTIFIED {
65-
// The state was set to NOTIFIED after we returned from the wait
66-
// but before we reset the state. Therefore, a wakeup is on its
67-
// way, which we need to consume here.
68-
// NOTICE: this is a priority hole.
69-
blocking_scalar(
70-
ticktimer_server(),
71-
TicktimerScalar::WaitForCondition(self.index(), 0).into(),
72-
)
73-
.expect("failed to send WaitForCondition command");
74-
}
73+
assert!(state == PARKED || state == NOTIFIED);
7574
}
7675

7776
pub fn unpark(self: Pin<&Self>) {
78-
let state = self.state.swap(NOTIFIED, Release);
79-
if state == PARKED {
80-
// The thread is parked, wake it up.
81-
blocking_scalar(
82-
ticktimer_server(),
83-
TicktimerScalar::NotifyCondition(self.index(), 1).into(),
84-
)
85-
.expect("failed to send NotifyCondition command");
77+
// If the state is already `NOTIFIED`, then another thread has
78+
// indicated it wants to wake up the target thread.
79+
//
80+
// If the state is `EMPTY` then there is nothing to wake up, and
81+
// the target thread will immediately exit from `park()` the
82+
// next time that function is called.
83+
if self.state.swap(NOTIFIED, Release) != PARKED {
84+
return;
85+
}
86+
87+
// The thread is parked, wake it up. Keep trying until we wake something up.
88+
// This will happen when the `NotifyCondition` call returns the fact that
89+
// 1 condition was notified.
90+
// Alternately, keep going until the state is seen as `EMPTY`, indicating
91+
// the thread woke up and kept going. This can happen when the Park
92+
// times out before we can send the NotifyCondition message.
93+
while blocking_scalar(
94+
ticktimer_server(),
95+
TicktimerScalar::NotifyCondition(self.index(), 1).into(),
96+
)
97+
.expect("failed to send NotifyCondition command")[0]
98+
== 0
99+
&& self.state.load(Acquire) != EMPTY
100+
{
101+
// The target thread hasn't yet hit the `WaitForCondition` call.
102+
// Yield to let the target thread run some more.
103+
crate::thread::yield_now();
86104
}
87105
}
88106
}

0 commit comments

Comments
 (0)