Skip to content

Commit

Permalink
feat(runnable): reintroduce into_raw/from_raw functions
Browse files Browse the repository at this point in the history
These APIs were removed prior to async-task 4.0 (in commit deb709f); they can however be useful to e.g. pass Runnable as a context argument to GCD's dispatch_async_f, which takes an opaque *mut () as an argument. Without exposing from_raw/into_raw one has to box the Runnable in order to get something that can be passed across that boundary, which seems wasteful.
  • Loading branch information
osiewicz authored Dec 21, 2023
1 parent dfa2374 commit b276d25
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 14 deletions.
11 changes: 2 additions & 9 deletions src/raw.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use alloc::alloc::Layout as StdLayout;
use core::cell::UnsafeCell;
use core::future::Future;
use core::marker::PhantomData;
use core::mem::{self, ManuallyDrop};
use core::pin::Pin;
use core::ptr::NonNull;
Expand Down Expand Up @@ -346,10 +345,7 @@ where
// Schedule the task. There is no need to call `Self::schedule(ptr)`
// because the schedule function cannot be destroyed while the waker is
// still alive.
let task = Runnable {
ptr: NonNull::new_unchecked(ptr as *mut ()),
_marker: PhantomData,
};
let task = Runnable::from_raw(NonNull::new_unchecked(ptr as *mut ()));
(*raw.schedule).schedule(task, ScheduleInfo::new(false));
}

Expand Down Expand Up @@ -438,10 +434,7 @@ where
_waker = Waker::from_raw(Self::clone_waker(ptr));
}

let task = Runnable {
ptr: NonNull::new_unchecked(ptr as *mut ()),
_marker: PhantomData,
};
let task = Runnable::from_raw(NonNull::new_unchecked(ptr as *mut ()));
(*raw.schedule).schedule(task, info);
}

Expand Down
76 changes: 72 additions & 4 deletions src/runnable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,10 +524,7 @@ impl<M> Builder<M> {
RawTask::<Fut, Fut::Output, S, M>::allocate(future, schedule, self)
};

let runnable = Runnable {
ptr,
_marker: PhantomData,
};
let runnable = Runnable::from_raw(ptr);
let task = Task {
ptr,
_marker: PhantomData,
Expand Down Expand Up @@ -820,6 +817,77 @@ impl<M> Runnable<M> {
fn header(&self) -> &Header<M> {
unsafe { &*(self.ptr.as_ptr() as *const Header<M>) }
}

/// Converts this task into a raw pointer.
///
/// To avoid a memory leak the pointer must be converted back to a Runnable using [`Runnable<M>::from_raw`][from_raw].
///
/// `into_raw` does not change the state of the [`Task`], but there is no guarantee that it will be in the same state after calling [`Runnable<M>::from_raw`][from_raw],
/// as the corresponding [`Task`] might have been dropped or cancelled.
///
/// # Examples
///
/// ```rust
/// use async_task::{Runnable, spawn};
/// let (runnable, task) = spawn(async {}, |_| {});
/// let runnable_pointer = runnable.into_raw();
///
/// unsafe {
/// // Convert back to an `Runnable` to prevent leak.
/// let runnable = Runnable::<()>::from_raw(runnable_pointer);
/// runnable.run();
/// // Further calls to `Runnable::from_raw(runnable_pointer)` would be memory-unsafe.
/// }
/// // The memory was freed when `x` went out of scope above, so `runnable_pointer` is now dangling!
/// ```
/// [from_raw]: #method.from_raw
pub fn into_raw(self) -> NonNull<()> {
let ptr = self.ptr;
mem::forget(self);
ptr
}

/// Converts a raw pointer into a Runnable.
///
/// # Safety
///
/// This method should only be used with raw pointers returned from [`Runnable<M>::into_raw`][into_raw].
/// It is not safe to use the provided pointer once it is passed to `from_raw`.
/// Crucially, it is unsafe to call `from_raw` multiple times with the same pointer - even if the resulting [`Runnable`] is not used -
/// as internally `async-task` uses reference counting.
///
/// It is however safe to call [`Runnable<M>::into_raw`][into_raw] on a [`Runnable`] created with `from_raw` or
/// after the [`Task`] associated with a given Runnable has been dropped or cancelled.
///
/// The state of the [`Runnable`] created with `from_raw` is not specified.
///
/// # Examples
///
/// ```rust
/// use async_task::{Runnable, spawn};
/// let (runnable, task) = spawn(async {}, |_| {});
/// let runnable_pointer = runnable.into_raw();
///
/// drop(task);
/// unsafe {
/// // Convert back to an `Runnable` to prevent leak.
/// let runnable = Runnable::<()>::from_raw(runnable_pointer);
/// let did_poll = runnable.run();
/// assert!(!did_poll);
/// // Further calls to `Runnable::from_raw(runnable_pointer)` would be memory-unsafe.
/// }
/// // The memory was freed when `x` went out of scope above, so `runnable_pointer` is now dangling!
/// ```
/// [into_raw]: #method.into_raw
pub unsafe fn from_raw(ptr: NonNull<()>) -> Self {
Self {
ptr,
_marker: Default::default(),
}
}
}

impl<M> Drop for Runnable<M> {
Expand Down
28 changes: 27 additions & 1 deletion tests/basic.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::ptr::NonNull;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll};

use async_task::Runnable;
Expand Down Expand Up @@ -297,3 +299,27 @@ fn waker() {
waker.wake();
r.recv().unwrap();
}

#[test]
fn raw() {
// Dispatch schedules a function for execution at a later point. For tests, we execute it straight away.
fn dispatch(trampoline: extern "C" fn(NonNull<()>), context: NonNull<()>) {
trampoline(context)
}
extern "C" fn trampoline(runnable: NonNull<()>) {
let task = unsafe { Runnable::<()>::from_raw(runnable) };
task.run();
}

let task_got_executed = Arc::new(AtomicBool::new(false));
let (runnable, _handle) = async_task::spawn(
{
let task_got_executed = task_got_executed.clone();
async move { task_got_executed.store(true, Ordering::SeqCst) }
},
|runnable: Runnable<()>| dispatch(trampoline, runnable.into_raw()),
);
runnable.schedule();

assert!(task_got_executed.load(Ordering::SeqCst));
}

0 comments on commit b276d25

Please sign in to comment.