Skip to content

Commit

Permalink
util: Add TokioContext future (#2791)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucio Franco <luciofranco14@gmail.com>
  • Loading branch information
blasrodri and LucioFranco committed Oct 14, 2020
1 parent a517dbf commit a204bc3
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 0 deletions.
1 change: 1 addition & 0 deletions tokio-util/Cargo.toml
Expand Up @@ -29,6 +29,7 @@ full = ["codec", "udp", "compat"]
compat = ["futures-io",]
codec = ["tokio/stream"]
udp = ["tokio/udp"]
rt = ["tokio/rt-core"]

[dependencies]
tokio = { version = "0.2.5", path = "../tokio" }
Expand Down
10 changes: 10 additions & 0 deletions tokio-util/src/cfg.rs
Expand Up @@ -18,6 +18,16 @@ macro_rules! cfg_compat {
}
}

macro_rules! cfg_rt {
($($item:item)*) => {
$(
#[cfg(feature = "rt")]
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
$item
)*
}
}

macro_rules! cfg_udp {
($($item:item)*) => {
$(
Expand Down
78 changes: 78 additions & 0 deletions tokio-util/src/context.rs
@@ -0,0 +1,78 @@
//! Tokio context aware futures utilities.
//!
//! This module includes utilities around integrating tokio with other runtimes
//! by allowing the context to be attached to futures. This allows spawning
//! futures on other executors while still using tokio to drive them. This
//! can be useful if you need to use a tokio based library in an executor/runtime
//! that does not provide a tokio context.

use pin_project_lite::pin_project;
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tokio::runtime::Handle;

pin_project! {
/// `TokioContext` allows connecting a custom executor with the tokio runtime.
///
/// It contains a `Handle` to the runtime. A handle to the runtime can be
/// obtain by calling the `Runtime::handle()` method.
pub struct TokioContext<F> {
#[pin]
inner: F,
handle: Handle,
}
}

impl<F: Future> Future for TokioContext<F> {
type Output = F::Output;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let me = self.project();
let handle = me.handle;
let fut = me.inner;

handle.enter(|| fut.poll(cx))
}
}

/// Trait extension that simplifies bundling a `Handle` with a `Future`.
pub trait HandleExt {
/// Convenience method that takes a Future and returns a `TokioContext`.
///
/// # Example: calling Tokio Runtime from a custom ThreadPool
///
/// ```no_run
/// use tokio_util::context::HandleExt;
/// use tokio::time::{delay_for, Duration};
///
/// let mut rt = tokio::runtime::Builder::new()
/// .threaded_scheduler()
/// .enable_all()
/// .build().unwrap();
///
/// let rt2 = tokio::runtime::Builder::new()
/// .threaded_scheduler()
/// .build().unwrap();
///
/// let fut = delay_for(Duration::from_millis(2));
///
/// rt.block_on(
/// rt2
/// .handle()
/// .wrap(async { delay_for(Duration::from_millis(2)).await }),
/// );
///```
fn wrap<F: Future>(&self, fut: F) -> TokioContext<F>;
}

impl HandleExt for Handle {
fn wrap<F: Future>(&self, fut: F) -> TokioContext<F> {
TokioContext {
inner: fut,
handle: self.clone(),
}
}
}
4 changes: 4 additions & 0 deletions tokio-util/src/lib.rs
Expand Up @@ -35,3 +35,7 @@ cfg_udp! {
cfg_compat! {
pub mod compat;
}

cfg_rt! {
pub mod context;
}
29 changes: 29 additions & 0 deletions tokio-util/tests/context.rs
@@ -0,0 +1,29 @@
#![warn(rust_2018_idioms)]
#![cfg(feature = "rt")]

use tokio::runtime::Builder;
use tokio::time::*;
use tokio_util::context::HandleExt;

#[test]
fn tokio_context_with_another_runtime() {
let mut rt1 = Builder::new()
.threaded_scheduler()
.core_threads(1)
// no timer!
.build()
.unwrap();
let rt2 = Builder::new()
.threaded_scheduler()
.core_threads(1)
.enable_all()
.build()
.unwrap();

// Without the `HandleExt.wrap()` there would be a panic because there is
// no timer running, since it would be referencing runtime r1.
let _ = rt1.block_on(
rt2.handle()
.wrap(async move { delay_for(Duration::from_millis(2)).await }),
);
}

0 comments on commit a204bc3

Please sign in to comment.