Skip to content

Commit 512e9de

Browse files
authored
rt: add LocalRuntime (#6808)
This change adds LocalRuntime, a new unstable runtime type which cannot be transferred across thread boundaries and supports spawn_local when called from the thread which owns the runtime. The initial set of docs for this are iffy. Documentation is absent right now at the module level, with the docs for the LocalRuntime struct itself being somewhat duplicative of those for the `Runtime` type. This can be addressed later as stabilization nears. This API has a few interesting implementation details: - because it was considered beneficial to reuse the same Handle as the normal runtime, it is possible to call spawn_local from a runtime context while on a different thread from the one which drives the runtime and owns it. This forces us to check the thread ID before attempting a local spawn. - An empty LocalOptions struct is passed into the build_local method in order to build the runtime. This will eventually have stuff in it like hooks. Relates to #6739.
1 parent 5ada511 commit 512e9de

File tree

12 files changed

+760
-16
lines changed

12 files changed

+760
-16
lines changed

tokio/src/runtime/builder.rs

+72-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
#![cfg_attr(loom, allow(unused_imports))]
22

33
use crate::runtime::handle::Handle;
4-
#[cfg(tokio_unstable)]
5-
use crate::runtime::TaskMeta;
64
use crate::runtime::{blocking, driver, Callback, HistogramBuilder, Runtime, TaskCallback};
5+
#[cfg(tokio_unstable)]
6+
use crate::runtime::{LocalOptions, LocalRuntime, TaskMeta};
77
use crate::util::rand::{RngSeed, RngSeedGenerator};
88

9+
use crate::runtime::blocking::BlockingPool;
10+
use crate::runtime::scheduler::CurrentThread;
911
use std::fmt;
1012
use std::io;
13+
use std::thread::ThreadId;
1114
use std::time::Duration;
1215

1316
/// Builds Tokio Runtime with custom configuration values.
@@ -800,6 +803,37 @@ impl Builder {
800803
}
801804
}
802805

806+
/// Creates the configured `LocalRuntime`.
807+
///
808+
/// The returned `LocalRuntime` instance is ready to spawn tasks.
809+
///
810+
/// # Panics
811+
/// This will panic if `current_thread` is not the selected runtime flavor.
812+
/// All other runtime flavors are unsupported by [`LocalRuntime`].
813+
///
814+
/// [`LocalRuntime`]: [crate::runtime::LocalRuntime]
815+
///
816+
/// # Examples
817+
///
818+
/// ```
819+
/// use tokio::runtime::Builder;
820+
///
821+
/// let rt = Builder::new_current_thread().build_local(&mut Default::default()).unwrap();
822+
///
823+
/// rt.block_on(async {
824+
/// println!("Hello from the Tokio runtime");
825+
/// });
826+
/// ```
827+
#[allow(unused_variables, unreachable_patterns)]
828+
#[cfg(tokio_unstable)]
829+
#[cfg_attr(docsrs, doc(cfg(tokio_unstable)))]
830+
pub fn build_local(&mut self, options: &LocalOptions) -> io::Result<LocalRuntime> {
831+
match &self.kind {
832+
Kind::CurrentThread => self.build_current_thread_local_runtime(),
833+
_ => panic!("Only current_thread is supported when building a local runtime"),
834+
}
835+
}
836+
803837
fn get_cfg(&self, workers: usize) -> driver::Cfg {
804838
driver::Cfg {
805839
enable_pause_time: match self.kind {
@@ -1191,8 +1225,40 @@ impl Builder {
11911225
}
11921226

11931227
fn build_current_thread_runtime(&mut self) -> io::Result<Runtime> {
1194-
use crate::runtime::scheduler::{self, CurrentThread};
1195-
use crate::runtime::{runtime::Scheduler, Config};
1228+
use crate::runtime::runtime::Scheduler;
1229+
1230+
let (scheduler, handle, blocking_pool) =
1231+
self.build_current_thread_runtime_components(None)?;
1232+
1233+
Ok(Runtime::from_parts(
1234+
Scheduler::CurrentThread(scheduler),
1235+
handle,
1236+
blocking_pool,
1237+
))
1238+
}
1239+
1240+
#[cfg(tokio_unstable)]
1241+
fn build_current_thread_local_runtime(&mut self) -> io::Result<LocalRuntime> {
1242+
use crate::runtime::local_runtime::LocalRuntimeScheduler;
1243+
1244+
let tid = std::thread::current().id();
1245+
1246+
let (scheduler, handle, blocking_pool) =
1247+
self.build_current_thread_runtime_components(Some(tid))?;
1248+
1249+
Ok(LocalRuntime::from_parts(
1250+
LocalRuntimeScheduler::CurrentThread(scheduler),
1251+
handle,
1252+
blocking_pool,
1253+
))
1254+
}
1255+
1256+
fn build_current_thread_runtime_components(
1257+
&mut self,
1258+
local_tid: Option<ThreadId>,
1259+
) -> io::Result<(CurrentThread, Handle, BlockingPool)> {
1260+
use crate::runtime::scheduler;
1261+
use crate::runtime::Config;
11961262

11971263
let (driver, driver_handle) = driver::Driver::new(self.get_cfg(1))?;
11981264

@@ -1227,17 +1293,14 @@ impl Builder {
12271293
seed_generator: seed_generator_1,
12281294
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
12291295
},
1296+
local_tid,
12301297
);
12311298

12321299
let handle = Handle {
12331300
inner: scheduler::Handle::CurrentThread(handle),
12341301
};
12351302

1236-
Ok(Runtime::from_parts(
1237-
Scheduler::CurrentThread(scheduler),
1238-
handle,
1239-
blocking_pool,
1240-
))
1303+
Ok((scheduler, handle, blocking_pool))
12411304
}
12421305

12431306
fn metrics_poll_count_histogram_builder(&self) -> Option<HistogramBuilder> {

tokio/src/runtime/handle.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ impl Handle {
250250
/// # Panics
251251
///
252252
/// This function panics if the provided future panics, if called within an
253-
/// asynchronous execution context, or if a timer future is executed on a
254-
/// runtime that has been shut down.
253+
/// asynchronous execution context, or if a timer future is executed on a runtime that has been
254+
/// shut down.
255255
///
256256
/// # Examples
257257
///
@@ -348,6 +348,31 @@ impl Handle {
348348
self.inner.spawn(future, id)
349349
}
350350

351+
#[track_caller]
352+
#[allow(dead_code)]
353+
pub(crate) unsafe fn spawn_local_named<F>(
354+
&self,
355+
future: F,
356+
_meta: SpawnMeta<'_>,
357+
) -> JoinHandle<F::Output>
358+
where
359+
F: Future + 'static,
360+
F::Output: 'static,
361+
{
362+
let id = crate::runtime::task::Id::next();
363+
#[cfg(all(
364+
tokio_unstable,
365+
tokio_taskdump,
366+
feature = "rt",
367+
target_os = "linux",
368+
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
369+
))]
370+
let future = super::task::trace::Trace::root(future);
371+
#[cfg(all(tokio_unstable, feature = "tracing"))]
372+
let future = crate::util::trace::task(future, "task", _meta, id.as_u64());
373+
self.inner.spawn_local(future, id)
374+
}
375+
351376
/// Returns the flavor of the current `Runtime`.
352377
///
353378
/// # Examples
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mod runtime;
2+
3+
mod options;
4+
5+
pub use options::LocalOptions;
6+
pub use runtime::LocalRuntime;
7+
pub(super) use runtime::LocalRuntimeScheduler;
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use std::marker::PhantomData;
2+
3+
/// `LocalRuntime`-only config options
4+
///
5+
/// Currently, there are no such options, but in the future, things like `!Send + !Sync` hooks may
6+
/// be added.
7+
#[derive(Default, Debug)]
8+
#[non_exhaustive]
9+
pub struct LocalOptions {
10+
/// Marker used to make this !Send and !Sync.
11+
_phantom: PhantomData<*mut u8>,
12+
}

0 commit comments

Comments
 (0)