diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/Cargo.toml | 2 | ||||
-rw-r--r-- | core/lib.rs | 1 | ||||
-rw-r--r-- | core/task.rs | 131 | ||||
-rw-r--r-- | core/task_queue.rs | 2 |
4 files changed, 134 insertions, 2 deletions
diff --git a/core/Cargo.toml b/core/Cargo.toml index c0854fdb6..2abf5b10e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -36,6 +36,7 @@ serde_json = { workspace = true, features = ["preserve_order"] } serde_v8.workspace = true smallvec.workspace = true sourcemap = "6.1" +tokio.workspace = true url.workspace = true v8.workspace = true @@ -46,4 +47,3 @@ path = "examples/http_bench_json_ops/main.rs" # These dependencies are only used for the 'http_bench_*_ops' examples. [dev-dependencies] deno_ast.workspace = true -tokio.workspace = true diff --git a/core/lib.rs b/core/lib.rs index 1b2841a66..58140bb22 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -23,6 +23,7 @@ mod resources; mod runtime; pub mod snapshot_util; mod source_map; +pub mod task; mod task_queue; // Re-exports diff --git a/core/task.rs b/core/task.rs new file mode 100644 index 000000000..46a4c8c26 --- /dev/null +++ b/core/task.rs @@ -0,0 +1,131 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use core::pin::Pin; +use core::task::Context; +use core::task::Poll; +use futures::Future; +use std::marker::PhantomData; +use tokio::runtime::Handle; +use tokio::runtime::RuntimeFlavor; + +/// Equivalent to [`tokio::task::JoinHandle`]. +#[repr(transparent)] +pub struct JoinHandle<R> { + handle: tokio::task::JoinHandle<MaskResultAsSend<R>>, + _r: PhantomData<R>, +} + +impl<R> JoinHandle<R> { + /// Equivalent to [`tokio::task::JoinHandle::abort`]. + pub fn abort(&self) { + self.handle.abort() + } +} + +impl<R> Future for JoinHandle<R> { + type Output = Result<R, tokio::task::JoinError>; + + fn poll( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll<Self::Output> { + // SAFETY: We are sure that handle is valid here + unsafe { + let me: &mut Self = Pin::into_inner_unchecked(self); + let handle = Pin::new_unchecked(&mut me.handle); + match handle.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Ok(r)) => Poll::Ready(Ok(r.into_inner())), + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + } + } + } +} + +/// Equivalent to [`tokio::task::spawn`], but does not require the future to be [`Send`]. Must only be +/// used on a [`RuntimeFlavor::CurrentThread`] executor, though this is only checked when running with +/// debug assertions. +pub fn spawn<F: Future<Output = R> + 'static, R: 'static>( + f: F, +) -> JoinHandle<R> { + debug_assert!( + Handle::current().runtime_flavor() == RuntimeFlavor::CurrentThread + ); + // SAFETY: we know this is a current-thread executor + let future = unsafe { MaskFutureAsSend::new(f) }; + JoinHandle { + handle: tokio::task::spawn(future), + _r: Default::default(), + } +} + +/// Equivalent to [`tokio::task::spawn_blocking`]. Currently a thin wrapper around the tokio API, but this +/// may change in the future. +pub fn spawn_blocking< + F: (FnOnce() -> R) + Send + 'static, + R: Send + 'static, +>( + f: F, +) -> JoinHandle<R> { + let handle = tokio::task::spawn_blocking(|| MaskResultAsSend { result: f() }); + JoinHandle { + handle, + _r: Default::default(), + } +} + +#[repr(transparent)] +#[doc(hidden)] +pub struct MaskResultAsSend<R> { + result: R, +} + +/// SAFETY: We ensure that Send bounds are only faked when tokio is running on a current-thread executor +unsafe impl<R> Send for MaskResultAsSend<R> {} + +impl<R> MaskResultAsSend<R> { + #[inline(always)] + pub fn into_inner(self) -> R { + self.result + } +} + +pub struct MaskFutureAsSend<F> { + future: F, +} + +impl<F> MaskFutureAsSend<F> { + /// Mark a non-`Send` future as `Send`. This is a trick to be able to use + /// `tokio::spawn()` (which requires `Send` futures) in a current thread + /// runtime. + /// + /// # Safety + /// + /// You must ensure that the future is actually used on the same + /// thread, ie. always use current thread runtime flavor from Tokio. + pub unsafe fn new(future: F) -> Self { + Self { future } + } +} + +// SAFETY: we are cheating here - this struct is NOT really Send, +// but we need to mark it Send so that we can use `spawn()` in Tokio. +unsafe impl<F> Send for MaskFutureAsSend<F> {} + +impl<F: Future> Future for MaskFutureAsSend<F> { + type Output = MaskResultAsSend<F::Output>; + + fn poll( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll<MaskResultAsSend<F::Output>> { + // SAFETY: We are sure that future is valid here + unsafe { + let me: &mut MaskFutureAsSend<F> = Pin::into_inner_unchecked(self); + let future = Pin::new_unchecked(&mut me.future); + match future.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(result) => Poll::Ready(MaskResultAsSend { result }), + } + } + } +} diff --git a/core/task_queue.rs b/core/task_queue.rs index 36a169650..adb25a4f6 100644 --- a/core/task_queue.rs +++ b/core/task_queue.rs @@ -127,7 +127,7 @@ mod tests { for i in 0..100 { let data = data.clone(); tasks.push(task_queue.queue(async move { - tokio::task::spawn_blocking(move || { + crate::task::spawn_blocking(move || { let mut data = data.lock(); if *data != i { panic!("Value was not equal."); |