Skip to content

Conversation

@ram-nad
Copy link

@ram-nad ram-nad commented Jan 6, 2026

Check #100 for details.

#100 (comment)
#100 (comment)
#100 (comment)

Summary:

Without M: Send bound on metadata, we can have unsound behavior. Specifically, as Waker is always Send + Sync, it can be dropped in any thread and in current implementation, we will drop M as well in the thread where we drop last Waker.

This PR does following changes:

  1. Add M: 'static bound on all the safe APIs, we need this because metadata will need to be valid as long as Runnable or Task is valid.

  2. We make sure that we drop M (metadata) whenever we have dropped both Runnable and Task.

  3. Add M: Send bound for Builder::spawn. This is required to avoid unsound behavior described above. We don't need this bound in Builder::spawn_local because, we require that Runnable be dropped/used on same thread if we use this API. Also Task: !Send if M: !Send. Therefore, because of change (2) we will never drop M on other threads if M: !Send.

  4. We add M: Sync bounds in .metadata() API for both Runnable and Task.

Closes #100

@ram-nad
Copy link
Author

ram-nad commented Jan 6, 2026

@notgull Should we change the existing example here?

@nurmohammed840
Copy link

Could you please split this into separate smaller pull requests for each change.
Large PRs are difficult to review.

@ram-nad
Copy link
Author

ram-nad commented Jan 6, 2026

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>,
Copy link
Author

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 ()),
Copy link
Author

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),
Copy link
Author

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);
Copy link
Author

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> {}
Copy link
Author

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
Copy link
Author

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,
Copy link
Author

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,
Copy link
Author

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);
Copy link
Author

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 ()) {
Copy link
Author

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 ()) {
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Fix Send and Sync bounds for M to avoiding unsound behavior.

2 participants