diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 86fa7abde..d85c6c885 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -17,6 +17,5 @@ proc-macro = true [features] -# Passes `?Send` to `async_trait` to force affected tasks to be spawned in the current thread. +# Do not use `async_trait` and do not add `Send` bounds. singlethreaded = [] - diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 217c839e0..be6213e9f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,16 +1,23 @@ use proc_macro::TokenStream; -/// This macro either emits `#[async_trait::async_trait]` or `#[async_trait::async_trait(?Send)]` -/// based on the activated feature set. +/// This macro emits `#[async_trait::async_trait]` if the `singlethreaded` feature is disabled. /// -/// This assumes that the `[async_trait](https://crates.io/crates/async-trait)` crate is imported -/// as `async_trait`. If the `singlethreaded` feature is enabled, `?Send` is passed to -/// `async_trait`, thereby forcing the affected asynchronous trait functions and methods to be run -/// in the same thread. +/// Starting from Rust 1.75.0, the `async_fn_in_trait` language feature is generally available +/// that allows traits to contain asynchronous methods and associated functions. However, the +/// feature has several known issues that are mainly related to Rust compiler's abilities to infer +/// `Send` bounds of associated types: [`90696``](https://github.com/rust-lang/rust/issues/90696). +/// +/// Therefore, if a trait requires `Send` bounds in its associated data types, this macro +/// circumvents the compiler shortcomings by using the +/// [`async-trait`](https://crates.io/crates/async-trait) crate which boxes return +/// [`Future`](https://doc.rust-lang.org/std/future/trait.Future.html) types of all the +/// asynchronous methods and associated functions of the trait. #[proc_macro_attribute] pub fn add_async_trait(_attr: TokenStream, item: TokenStream) -> TokenStream { if cfg!(feature = "singlethreaded") { - let mut output = "#[async_trait::async_trait(?Send)]".parse::().unwrap(); + // `async_fn_in_trait` requires the user to explicitly specify the `Send` bound for public + // trait methods, however the `singlethreaded` feature renders the requirement irrelevant. + let mut output = "#[allow(async_fn_in_trait)]".parse::().unwrap(); output.extend(item); output } else { diff --git a/openraft/src/docs/feature_flags/feature-flags.md b/openraft/src/docs/feature_flags/feature-flags.md index de37b9454..3e6b64846 100644 --- a/openraft/src/docs/feature_flags/feature-flags.md +++ b/openraft/src/docs/feature_flags/feature-flags.md @@ -47,6 +47,8 @@ By default openraft enables no features. - `singlethreaded`: removes `Send` and `Sync` bounds from `AppData`, `AppDataResponse`, `RaftEntry`, `SnapshotData` and other types to force the asynchronous runtime to spawn any tasks in the current thread. This is for any single-threaded application that never allows a raft instance to be shared among multiple threads. + This feature relies on the `async_fn_in_trait` language feature that is officially supported from Rust 1.75.0. + If the feature is enabled, affected asynchronous trait methods and associated functions no longer use `async_trait`. In order to use the feature, `AsyncRuntime::spawn` should invoke `tokio::task::spawn_local` or equivalents.