From 0e40b89f378ea23efd615130ed560dfb1a3e25ed Mon Sep 17 00:00:00 2001 From: richarddd Date: Fri, 24 Apr 2026 11:09:41 +0200 Subject: [PATCH] fix: make async runtime and context Send/Sync without parallel feature (#495) --- CHANGELOG.md | 1 + core/src/context/async.rs | 7 ------- core/src/context/async/future.rs | 1 - core/src/context/owner.rs | 1 - core/src/runtime/async.rs | 23 ++++++++++------------- 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfc5ff1f0..f6fa3f874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed iterators to use correct IteratorPrototype chain - Fixed a latent ABI layout vulnerability in `JS_NewPromiseCapability` FFI boundary by replacing tuple with strictly compatible array - `#[rquickjs::class]` now rejects fields whose type implements `JsClass` with a clear compile error; such fields silently dropped nested mutations because the generated getter cloned the value. Wrap the field in `Class<'js, T>` instead #[532](https://github.com/DelSkayn/rquickjs/issues/532) +- Made `AsyncRuntime`, `AsyncContext`, and `WithFuture` unconditionally `Send`/`Sync` so `async_with` works with multi-threaded executors without requiring the `parallel` feature #[495](https://github.com/DelSkayn/rquickjs/issues/495) ## [0.11.0] - 2025-12-16 diff --git a/core/src/context/async.rs b/core/src/context/async.rs index fa5dd5f90..e661eb7b9 100644 --- a/core/src/context/async.rs +++ b/core/src/context/async.rs @@ -241,13 +241,6 @@ impl AsyncContext { } } -// Since the reference to runtime is behind a Arc this object is send -#[cfg(feature = "parallel")] -unsafe impl Send for AsyncContext {} - -// Since all functions lock the global runtime lock access is synchronized so -// this object is sync -#[cfg(feature = "parallel")] unsafe impl Sync for AsyncContext {} #[cfg(test)] diff --git a/core/src/context/async/future.rs b/core/src/context/async/future.rs index d42d601c8..581624c85 100644 --- a/core/src/context/async/future.rs +++ b/core/src/context/async/future.rs @@ -157,5 +157,4 @@ where } } -#[cfg(feature = "parallel")] unsafe impl Send for WithFuture<'_, F, R> {} diff --git a/core/src/context/owner.rs b/core/src/context/owner.rs index 255179963..12f239eaa 100644 --- a/core/src/context/owner.rs +++ b/core/src/context/owner.rs @@ -11,7 +11,6 @@ pub(crate) trait DropContext: Clone { unsafe fn drop_context(&self, ctx: NonNull); } -#[cfg(feature = "parallel")] unsafe impl Send for ContextOwner {} /// Struct in charge of dropping contexts when they go out of scope diff --git a/core/src/runtime/async.rs b/core/src/runtime/async.rs index 52949cea1..37041fd43 100644 --- a/core/src/runtime/async.rs +++ b/core/src/runtime/async.rs @@ -47,8 +47,8 @@ impl Drop for InnerRuntime { } } -#[cfg(feature = "parallel")] unsafe impl Send for InnerRuntime {} +unsafe impl Sync for InnerRuntime {} /// A weak handle to the async runtime. /// @@ -83,17 +83,13 @@ pub struct AsyncRuntime { // Since all functions which use runtime are behind a mutex // sending the runtime to other threads should be fine. -#[cfg(feature = "parallel")] unsafe impl Send for AsyncRuntime {} -#[cfg(feature = "parallel")] unsafe impl Send for AsyncWeakRuntime {} // Since a global lock needs to be locked for safe use // using runtime in a sync way should be safe as // simultaneous accesses is synchronized behind a lock. -#[cfg(feature = "parallel")] unsafe impl Sync for AsyncRuntime {} -#[cfg(feature = "parallel")] unsafe impl Sync for AsyncWeakRuntime {} impl AsyncRuntime { @@ -640,27 +636,28 @@ mod test { rt.idle().await; }); - #[cfg(feature = "parallel")] fn assert_is_send(t: T) -> T { t } - #[cfg(feature = "parallel")] - fn assert_is_sync(t: T) -> T { + fn assert_is_sync(t: T) -> T { t } - #[cfg(feature = "parallel")] #[tokio::test] async fn ensure_types_are_send_sync() { let rt = AsyncRuntime::new().unwrap(); - std::mem::drop(assert_is_sync(rt.idle())); - std::mem::drop(assert_is_sync(rt.execute_pending_job())); - std::mem::drop(assert_is_sync(rt.drive())); - std::mem::drop(assert_is_send(rt.idle())); std::mem::drop(assert_is_send(rt.execute_pending_job())); std::mem::drop(assert_is_send(rt.drive())); + + std::mem::drop(assert_is_send(rt.clone())); + std::mem::drop(assert_is_sync(rt.clone())); + + let ctx = crate::AsyncContext::full(&rt).await.unwrap(); + std::mem::drop(assert_is_send(ctx.clone())); + std::mem::drop(assert_is_sync(ctx.clone())); + std::mem::drop(assert_is_send(ctx.async_with(async |_| {}))); } }