diff --git a/src/runnable.rs b/src/runnable.rs index cada62d..c7659c9 100644 --- a/src/runnable.rs +++ b/src/runnable.rs @@ -720,6 +720,23 @@ impl Runnable { &self.header_with_metadata().metadata } + /// Get a pointer to the metadata associated with this task. + /// + /// Unlike [`Runnable::metadata`], this doesn't create any intermediate reference to + /// the task or the metadata, and doesn't require a reference to the task. + /// + /// # Safety + /// + /// Calling this method is always safe, but dereferencing the returned pointer isn't. + /// The pointer is only guaranteed to be valid for the scope between matching calls to + /// [`Runnable::into_raw`] and [`Runnable::from_raw`]. The returned pointer aliases the + /// references returned by [`Runnable::metadata`] and [`Task::metadata`]. + pub fn metadata_raw(ptr: NonNull<()>) -> NonNull { + let header: *mut HeaderWithMetadata = ptr.cast().as_ptr(); + // TODO: Once the MSRV reaches 1.82 this can use &raw mut instead + unsafe { NonNull::new_unchecked(core::ptr::addr_of_mut!((*header).metadata)) } + } + /// Schedules the task. /// /// This is a convenience method that passes the [`Runnable`] to the schedule function. diff --git a/tests/metadata.rs b/tests/metadata.rs index d3d8d53..f61dacd 100644 --- a/tests/metadata.rs +++ b/tests/metadata.rs @@ -2,7 +2,10 @@ use async_task::{Builder, Runnable}; use flume::unbounded; use smol::future; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + ptr::NonNull, + sync::atomic::{AtomicUsize, Ordering}, +}; #[test] fn metadata_use_case() { @@ -56,3 +59,43 @@ fn metadata_use_case() { t2.await; }); } + +#[test] +fn metadata_raw() { + let (sender, receiver) = unbounded::>(); + + let future = |counter: &AtomicUsize| { + assert_eq!(0, counter.fetch_add(1, Ordering::SeqCst)); + async {} + }; + + let schedule = move |runnable: Runnable| { + let ptr = runnable.into_raw(); + + { + let counter: &AtomicUsize = unsafe { Runnable::metadata_raw(ptr).as_ref() }; + assert_eq!(1, counter.fetch_add(1, Ordering::SeqCst)); + } + + sender.send(ptr).ok(); + }; + + let (runnable, task) = unsafe { + Builder::new() + .metadata(AtomicUsize::new(0)) + .spawn_unchecked(future, schedule) + }; + + runnable.schedule(); + let ptr = receiver.recv().unwrap(); + + { + let counter: &AtomicUsize = unsafe { Runnable::metadata_raw(ptr).as_ref() }; + assert_eq!(2, counter.fetch_add(1, Ordering::SeqCst)); + } + + let runnable: Runnable = unsafe { Runnable::from_raw(ptr) }; + assert_eq!(3, runnable.metadata().fetch_add(1, Ordering::SeqCst)); + + assert_eq!(4, task.metadata().load(Ordering::SeqCst)); +}