Skip to content

Commit b276d25

Browse files
authored
feat(runnable): reintroduce into_raw/from_raw functions
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.
1 parent dfa2374 commit b276d25

File tree

3 files changed

+101
-14
lines changed

3 files changed

+101
-14
lines changed

Diff for: src/raw.rs

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use alloc::alloc::Layout as StdLayout;
22
use core::cell::UnsafeCell;
33
use core::future::Future;
4-
use core::marker::PhantomData;
54
use core::mem::{self, ManuallyDrop};
65
use core::pin::Pin;
76
use core::ptr::NonNull;
@@ -346,10 +345,7 @@ where
346345
// Schedule the task. There is no need to call `Self::schedule(ptr)`
347346
// because the schedule function cannot be destroyed while the waker is
348347
// still alive.
349-
let task = Runnable {
350-
ptr: NonNull::new_unchecked(ptr as *mut ()),
351-
_marker: PhantomData,
352-
};
348+
let task = Runnable::from_raw(NonNull::new_unchecked(ptr as *mut ()));
353349
(*raw.schedule).schedule(task, ScheduleInfo::new(false));
354350
}
355351

@@ -438,10 +434,7 @@ where
438434
_waker = Waker::from_raw(Self::clone_waker(ptr));
439435
}
440436

441-
let task = Runnable {
442-
ptr: NonNull::new_unchecked(ptr as *mut ()),
443-
_marker: PhantomData,
444-
};
437+
let task = Runnable::from_raw(NonNull::new_unchecked(ptr as *mut ()));
445438
(*raw.schedule).schedule(task, info);
446439
}
447440

Diff for: src/runnable.rs

+72-4
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,7 @@ impl<M> Builder<M> {
524524
RawTask::<Fut, Fut::Output, S, M>::allocate(future, schedule, self)
525525
};
526526

527-
let runnable = Runnable {
528-
ptr,
529-
_marker: PhantomData,
530-
};
527+
let runnable = Runnable::from_raw(ptr);
531528
let task = Task {
532529
ptr,
533530
_marker: PhantomData,
@@ -820,6 +817,77 @@ impl<M> Runnable<M> {
820817
fn header(&self) -> &Header<M> {
821818
unsafe { &*(self.ptr.as_ptr() as *const Header<M>) }
822819
}
820+
821+
/// Converts this task into a raw pointer.
822+
///
823+
/// To avoid a memory leak the pointer must be converted back to a Runnable using [`Runnable<M>::from_raw`][from_raw].
824+
///
825+
/// `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],
826+
/// as the corresponding [`Task`] might have been dropped or cancelled.
827+
///
828+
/// # Examples
829+
///
830+
/// ```rust
831+
/// use async_task::{Runnable, spawn};
832+
833+
/// let (runnable, task) = spawn(async {}, |_| {});
834+
/// let runnable_pointer = runnable.into_raw();
835+
///
836+
/// unsafe {
837+
/// // Convert back to an `Runnable` to prevent leak.
838+
/// let runnable = Runnable::<()>::from_raw(runnable_pointer);
839+
/// runnable.run();
840+
/// // Further calls to `Runnable::from_raw(runnable_pointer)` would be memory-unsafe.
841+
/// }
842+
/// // The memory was freed when `x` went out of scope above, so `runnable_pointer` is now dangling!
843+
/// ```
844+
/// [from_raw]: #method.from_raw
845+
pub fn into_raw(self) -> NonNull<()> {
846+
let ptr = self.ptr;
847+
mem::forget(self);
848+
ptr
849+
}
850+
851+
/// Converts a raw pointer into a Runnable.
852+
///
853+
/// # Safety
854+
///
855+
/// This method should only be used with raw pointers returned from [`Runnable<M>::into_raw`][into_raw].
856+
/// It is not safe to use the provided pointer once it is passed to `from_raw`.
857+
/// Crucially, it is unsafe to call `from_raw` multiple times with the same pointer - even if the resulting [`Runnable`] is not used -
858+
/// as internally `async-task` uses reference counting.
859+
///
860+
/// It is however safe to call [`Runnable<M>::into_raw`][into_raw] on a [`Runnable`] created with `from_raw` or
861+
/// after the [`Task`] associated with a given Runnable has been dropped or cancelled.
862+
///
863+
/// The state of the [`Runnable`] created with `from_raw` is not specified.
864+
///
865+
/// # Examples
866+
///
867+
/// ```rust
868+
/// use async_task::{Runnable, spawn};
869+
870+
/// let (runnable, task) = spawn(async {}, |_| {});
871+
/// let runnable_pointer = runnable.into_raw();
872+
///
873+
/// drop(task);
874+
/// unsafe {
875+
/// // Convert back to an `Runnable` to prevent leak.
876+
/// let runnable = Runnable::<()>::from_raw(runnable_pointer);
877+
/// let did_poll = runnable.run();
878+
/// assert!(!did_poll);
879+
/// // Further calls to `Runnable::from_raw(runnable_pointer)` would be memory-unsafe.
880+
/// }
881+
/// // The memory was freed when `x` went out of scope above, so `runnable_pointer` is now dangling!
882+
/// ```
883+
884+
/// [into_raw]: #method.into_raw
885+
pub unsafe fn from_raw(ptr: NonNull<()>) -> Self {
886+
Self {
887+
ptr,
888+
_marker: Default::default(),
889+
}
890+
}
823891
}
824892

825893
impl<M> Drop for Runnable<M> {

Diff for: tests/basic.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::future::Future;
22
use std::pin::Pin;
3-
use std::sync::atomic::{AtomicUsize, Ordering};
3+
use std::ptr::NonNull;
4+
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
5+
use std::sync::Arc;
46
use std::task::{Context, Poll};
57

68
use async_task::Runnable;
@@ -297,3 +299,27 @@ fn waker() {
297299
waker.wake();
298300
r.recv().unwrap();
299301
}
302+
303+
#[test]
304+
fn raw() {
305+
// Dispatch schedules a function for execution at a later point. For tests, we execute it straight away.
306+
fn dispatch(trampoline: extern "C" fn(NonNull<()>), context: NonNull<()>) {
307+
trampoline(context)
308+
}
309+
extern "C" fn trampoline(runnable: NonNull<()>) {
310+
let task = unsafe { Runnable::<()>::from_raw(runnable) };
311+
task.run();
312+
}
313+
314+
let task_got_executed = Arc::new(AtomicBool::new(false));
315+
let (runnable, _handle) = async_task::spawn(
316+
{
317+
let task_got_executed = task_got_executed.clone();
318+
async move { task_got_executed.store(true, Ordering::SeqCst) }
319+
},
320+
|runnable: Runnable<()>| dispatch(trampoline, runnable.into_raw()),
321+
);
322+
runnable.schedule();
323+
324+
assert!(task_got_executed.load(Ordering::SeqCst));
325+
}

0 commit comments

Comments
 (0)