Skip to content

Commit 058347d

Browse files
committed
feat(rt/tokio): introduce HandleExecutor
this commit introduces an `hyper::rt::Executor` implementation, `HandleExecutor`. this executor is used to run tasks on a tokio runtime, but spawns tasks onto a particular runtime using a provided `tokio::runtime::Handle` rather than by calling `tokio::spawn()` this is useful for situations in which you wish to run tasks on a *separate* runtime, for example. this may be applicable to those configuring hyper clients and servers in applications running on NUMA (Non-Uniform Memory Awareneses) systems, or those that wish to manage provision separate resources for background tasks. see the [`tokio::runtime`] documentation for more information about choosing the correct runtime for an application. a unit test is included which demonstrates that `HandleExecutor` can be used to spawn background tasks onto a separate tokio runtime. [`tokio::runtime`]: https://docs.rs/tokio/latest/tokio/runtime/index.html#numa-awareness Signed-off-by: katelyn martin <git@katelyn.world>
1 parent ed8f666 commit 058347d

2 files changed

Lines changed: 130 additions & 2 deletions

File tree

‎src/rt/tokio.rs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ use std::{
6060
use hyper::rt::{Sleep, Timer};
6161
use pin_project_lite::pin_project;
6262

63-
pub use self::{executor::TokioExecutor, with_hyper_io::WithHyperIo, with_tokio_io::WithTokioIo};
63+
pub use self::{
64+
executor::{HandleExecutor, TokioExecutor},
65+
with_hyper_io::WithHyperIo,
66+
with_tokio_io::WithTokioIo,
67+
};
6468

6569
mod executor;
6670
mod with_hyper_io;

‎src/rt/tokio/executor.rs‎

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@ use tracing::instrument::Instrument;
2121
#[derive(Default, Debug, Clone)]
2222
pub struct TokioExecutor {}
2323

24+
/// Future executor backed by a runtime [`Handle`].
25+
///
26+
/// This executor, like [`TokioExecutor`], utilises [`tokio`] threads. This
27+
/// executor spawns tasks using [`Handle::spawn()`] rather than
28+
/// [`tokio::spawn()`], however.
29+
///
30+
/// A runtime handle may be obtained by calling [`Runtime::handle()`].
31+
///
32+
/// This is useful for situations in which you wish to run tasks on a
33+
/// *separate* runtime. If your application only runs using a single tokio
34+
/// runtime, [`TokioExecutor`] should be used instead.
35+
///
36+
/// This may be applicable to those configuring hyper clients and servers in
37+
/// applications running on NUMA (Non-Uniform Memory Awareneses) systems, or
38+
/// if you wish to manage provision separate resources for background tasks
39+
/// associated with a client or server.
40+
///
41+
/// See the [`tokio::runtime`] documentation for more information about
42+
/// choosing the correct runtime for your application.
43+
///
44+
/// # Examples
45+
///
46+
/// ```
47+
/// use hyper_util::rt::tokio::HandleExecutor;
48+
///
49+
/// let runtime = tokio::runtime::Builder::new_current_thread()
50+
/// .build()
51+
/// .unwrap();
52+
/// let handle = runtime.handle().clone();
53+
/// let executor = HandleExecutor::new(handle);
54+
/// ```
55+
///
56+
/// [`Handle`]: tokio::runtime::Handle
57+
/// [`Handle::spawn()`]: tokio::runtime::Handle::spawn
58+
/// [`Runtime::handle()`]: tokio::runtime::Runtime::handle
59+
pub struct HandleExecutor {
60+
handle: tokio::runtime::Handle,
61+
}
62+
2463
// ===== impl TokioExecutor =====
2564

2665
impl<Fut> Executor<Fut> for TokioExecutor
@@ -44,9 +83,28 @@ impl TokioExecutor {
4483
}
4584
}
4685

86+
// ===== impl HandleExecutor =====
87+
88+
impl HandleExecutor {
89+
/// TK
90+
pub fn new(handle: tokio::runtime::Handle) -> Self {
91+
Self { handle }
92+
}
93+
}
94+
95+
impl<Fut> Executor<Fut> for HandleExecutor
96+
where
97+
Fut: Future + Send + 'static,
98+
Fut::Output: Send + 'static,
99+
{
100+
fn execute(&self, fut: Fut) {
101+
self.handle.spawn(fut);
102+
}
103+
}
104+
47105
#[cfg(test)]
48106
mod tests {
49-
use crate::rt::TokioExecutor;
107+
use crate::rt::tokio::{HandleExecutor, TokioExecutor};
50108
use hyper::rt::Executor;
51109
use tokio::sync::oneshot;
52110

@@ -84,4 +142,70 @@ mod tests {
84142
assert_eq!(spawned_span, None);
85143
}
86144
}
145+
146+
#[test]
147+
fn handle_executor_can_execute_task_on_separate_runtime() {
148+
// Create a "foreground" runtime we will run our top-level on.
149+
let rt = tokio::runtime::Builder::new_current_thread()
150+
.worker_threads(1)
151+
.name("foreground")
152+
.build()
153+
.unwrap();
154+
155+
// Create a "background" runtime, whose handle will be used to spawn
156+
// background tasks by our executor.
157+
let background = tokio::runtime::Builder::new_current_thread()
158+
.worker_threads(1)
159+
.name("background")
160+
.build()
161+
.unwrap();
162+
let handle = background.handle().clone();
163+
let executor = HandleExecutor::new(handle);
164+
165+
// Begin running the background runtime on a separate worker thread.
166+
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
167+
let worker = std::thread::Builder::new()
168+
.name("execute-task-on-separate-runtime-worker".into())
169+
.spawn(move || {
170+
use futures_util::FutureExt;
171+
let fut = shutdown_rx.map(drop);
172+
background.block_on(fut);
173+
})
174+
.expect("should spawn thread");
175+
176+
// Run a future that, when polled, spawns a background task onto the
177+
// handle executor. This background task retrieves the name of the
178+
// runtime that it is running on, and sends the name back to its
179+
// caller. The parent then asserts that the child was run on the
180+
// "background" runtime.
181+
rt.block_on(async move {
182+
let handle = tokio::runtime::Handle::current();
183+
let name = handle.name().unwrap().to_string();
184+
assert_eq!(
185+
name, "foreground",
186+
"future should be spawned onto foreground runtime"
187+
);
188+
189+
let (tx, rx) = oneshot::channel();
190+
let fut = async move {
191+
let handle = tokio::runtime::Handle::current();
192+
let name = handle.name().unwrap().to_string();
193+
tx.send(name).unwrap();
194+
};
195+
196+
executor.execute(fut);
197+
let name = rx.await.unwrap();
198+
assert_eq!(
199+
name, "background",
200+
"worker should be spawned onto background runtime"
201+
);
202+
});
203+
204+
// Signal to the background runtime that it should shutdown now, and
205+
// then wait for the thread running it to finish.
206+
shutdown_tx
207+
.send(())
208+
.expect("shutdown signal should be sent");
209+
worker.join().expect("worker thread should finish");
210+
}
87211
}

0 commit comments

Comments
 (0)