-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sync: fix racy UnsafeCell
access on a closed oneshot
#4226
Changes from all commits
844dc9b
c8f82d4
5ae003c
a6b60ed
54b740d
2324301
ef15fba
897e71e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1031,11 +1031,38 @@ impl State { | |||||||||||
} | ||||||||||||
|
||||||||||||
fn set_complete(cell: &AtomicUsize) -> State { | ||||||||||||
// TODO: This could be `Release`, followed by an `Acquire` fence *if* | ||||||||||||
// the `RX_TASK_SET` flag is set. However, `loom` does not support | ||||||||||||
// fences yet. | ||||||||||||
let val = cell.fetch_or(VALUE_SENT, AcqRel); | ||||||||||||
State(val) | ||||||||||||
// This method is a compare-and-swap loop rather than a fetch-or like | ||||||||||||
// other `set_$WHATEVER` methods on `State`. This is because we must | ||||||||||||
// check if the state has been closed before setting the `VALUE_SENT` | ||||||||||||
// bit. | ||||||||||||
// | ||||||||||||
// We don't want to set both the `VALUE_SENT` bit if the `CLOSED` | ||||||||||||
// bit is already set, because `VALUE_SENT` will tell the receiver that | ||||||||||||
// it's okay to access the inner `UnsafeCell`. Immediately after calling | ||||||||||||
// `set_complete`, if the channel was closed, the sender will _also_ | ||||||||||||
// access the `UnsafeCell` to take the value back out, so if a | ||||||||||||
// `poll_recv` or `try_recv` call is occurring concurrently, both | ||||||||||||
// threads may try to access the `UnsafeCell` if we were to set the | ||||||||||||
// `VALUE_SENT` bit on a closed channel. | ||||||||||||
let mut state = cell.load(Ordering::Relaxed); | ||||||||||||
loop { | ||||||||||||
if State(state).is_closed() { | ||||||||||||
break; | ||||||||||||
} | ||||||||||||
Comment on lines
+1048
to
+1051
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also considered writing this as
Suggested change
but i had a vague memory of @carllerche saying that he doesn't like the use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the way it's written here with a |
||||||||||||
// TODO: This could be `Release`, followed by an `Acquire` fence *if* | ||||||||||||
// the `RX_TASK_SET` flag is set. However, `loom` does not support | ||||||||||||
// fences yet. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think loom has partial support for fences iiuc (tokio-rs/loom#220), so technically this comment isn't correct anymore :D. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think there might be a couple other places in Tokio where we also can implement some things nicer now that |
||||||||||||
match cell.compare_exchange_weak( | ||||||||||||
state, | ||||||||||||
state | VALUE_SENT, | ||||||||||||
Ordering::AcqRel, | ||||||||||||
Ordering::Acquire, | ||||||||||||
) { | ||||||||||||
Ok(_) => break, | ||||||||||||
Err(actual) => state = actual, | ||||||||||||
} | ||||||||||||
} | ||||||||||||
State(state) | ||||||||||||
} | ||||||||||||
|
||||||||||||
fn is_rx_task_set(self) -> bool { | ||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems safer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's necessary; the
compare_exchange
has anAcquire
failure ordering, so when we actually go to set the value, we will always perform anAcquire
operation, and if the initialRelaxed
load is stale, the CAS will fail, and we'll loop again with a value that was accessed with anAcquire
ordering. So, making the initial loadRelaxed
really just serves to avoid an unnecessaryAcquire
in the case that we're the only thread writing to the state; if it's contended and theRelaxed
load is stale, we will always perform anAcquire
on subsequent iterations.Other CAS loops elsewhere in Tokio follow a similar pattern:
tokio/tokio/src/time/driver/entry.rs
Lines 167 to 192 in 03969cd
However, I'm happy to change this if you prefer.