-
Notifications
You must be signed in to change notification settings - Fork 56
Fix: Make Metadata Send to avoid unsound behavior #101
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
base: master
Are you sure you want to change the base?
Conversation
|
@notgull Should we change the existing example here? |
|
Could you please split this into separate smaller pull requests for each change. |
|
Most changes in PR are due to (2). I am not sure if we can split it further. Also, (3) is dependent on (2) so we have it this way. I can add comments in the changes tab to specify what is being done if that makes it simpler to review? |
| /// | ||
| /// This metadata will always be manually dropped, | ||
| /// whenever `Runnable` and `Task` are both dropped. | ||
| pub(crate) metadata: ManuallyDrop<M>, |
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.
We will drop metadata separately from task, so mark it as ManuallyDrop.
| pub(crate) drop_future: unsafe fn(*const (), &TaskLayout), | ||
|
|
||
| /// Drops the metadata associated with the task. | ||
| pub(crate) drop_metadata: unsafe fn(*const ()), |
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 function will be used to drop metadata. This is used when dropping the Task.
| propagate_panic, | ||
| }, | ||
| metadata, | ||
| metadata: core::mem::ManuallyDrop::new(metadata), |
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.
We need to initialize metadata as ManuallyDrop (inside allocate_task)
|
|
||
| // Drop the task reference. | ||
| drop_ref(ptr); | ||
| drop_runnable::<M>(state, ptr); |
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.
We are dropping Runnable here, using the new helper function.
|
|
||
| unsafe impl<M: Send + Sync> Send for Runnable<M> {} | ||
| unsafe impl<M: Send + Sync> Sync for Runnable<M> {} | ||
| unsafe impl<M> Send for Runnable<M> {} |
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.
Update bounds for Runnable. It will always be Send (as Waker: Send). It will be Sync only if M: Sync. Note we don't need M: Send for it to be Sync as we cannot drop metadata using its reference to Runnable.
| /// Tasks can be created with a metadata object associated with them; by default, this | ||
| /// is a `()` value. See the [`Builder::metadata()`] method for more information. | ||
| pub fn metadata(&self) -> &M { | ||
| pub fn metadata(&self) -> &M |
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.
If M: !Sync it will be unsafe to have this function as we could access metadata from both Task and Runnable which could be in different threads. So, we need this bound here.
| /// ``` | ||
| pub fn spawn<F, Fut, S>(self, future: F, schedule: S) -> (Runnable<M>, Task<Fut::Output, M>) | ||
| where | ||
| M: Send + 'static, |
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.
Since the Future is Send here, we can poll it from any thread. So, we need M: Send as well.
| schedule: S, | ||
| ) -> (Runnable<M>, Task<Fut::Output, M>) | ||
| where | ||
| M: 'static, |
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 is spawn_local, we don't need Send here as described in the PR.
| // | ||
| // Safe to call as metadata wouldn't have been | ||
| // dropped before, because we just removed `Task` reference. | ||
| ((*header).vtable.drop_metadata)(ptr); |
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.
We are dropping Task here, therefore, we need to drop metadata as well. (We are sure that Runnable has been dropped at this point)
| /// The task must be closed before this function is called. Also, | ||
| /// we need to make sure that both Runnable and Task are dropped. | ||
| #[inline] | ||
| pub(crate) unsafe fn drop_metadata<M>(ptr: *const ()) { |
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.
New function to drop metadata.
| /// This is a helper function that we call whenever we are dropping | ||
| /// task reference (as Runnable) and the task is already completed/closed. | ||
| #[inline] | ||
| pub(crate) unsafe fn drop_runnable<M>(state: usize, ptr: *const ()) { |
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.
Helper function to drop Runnable. Every time we drop Runnable, we need to check if Task has already been detached. If it has been then we need to drop metadata as well.
Check #100 for details.
#100 (comment)
#100 (comment)
#100 (comment)
Summary:
Without
M: Sendbound on metadata, we can have unsound behavior. Specifically, asWakeris alwaysSend + Sync, it can be dropped in any thread and in current implementation, we will dropMas well in the thread where we drop lastWaker.This PR does following changes:
Add
M: 'staticbound on all the safe APIs, we need this because metadata will need to be valid as long asRunnableorTaskis valid.We make sure that we drop
M(metadata) whenever we have dropped bothRunnableandTask.Add
M: Sendbound forBuilder::spawn. This is required to avoid unsound behavior described above. We don't need this bound inBuilder::spawn_localbecause, we require thatRunnablebe dropped/used on same thread if we use this API. AlsoTask: !SendifM: !Send. Therefore, because of change (2) we will never dropMon other threads ifM: !Send.We add
M: Syncbounds in.metadata()API for bothRunnableandTask.Closes #100