mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-07 00:00:09 +02:00
## Motivation The `futures` crate's [`compat` module][futures-compat] provides interoperability between `futures` 0.1 and `std::future` _future types_ (e.g. implementing `std::future::Future` for a type that implements the `futures` 0.1 `Future` trait). However, this on its own is insufficient to run code written against `tokio` 0.1 on a `tokio` 0.2 runtime, if that code also relies on `tokio`'s runtime services. If legacy tasks are executed that rely on `tokio::timer`, perform IO using `tokio`'s reactor, or call `tokio::spawn`, those API calls will fail unless there is also a runtime compatibility layer. ## Solution As proposed in #1549, this branch introduces a new `tokio-compat` crate, with implementations of the thread pool and current-thread runtimes that are capable of running both tokio 0.1 and tokio 0.2 tasks. The compat runtime creates a background thread that runs a `tokio` 0.1 timer and reactor, and sets itself as the `tokio` 0.1 executor as well as the default 0.2 executor. This allows 0.1 futures that use 0.1 timer, reactor, and executor APIs may run alongside `std::future` tasks on the 0.2 runtime. ### Examples Spawning both `tokio` 0.1 and `tokio` 0.2 futures: ```rust use futures_01::future::lazy; tokio_compat::run(lazy(|| { // spawn a `futures` 0.1 future using the `spawn` function from the // `tokio` 0.1 crate: tokio_01::spawn(lazy(|| { println!("hello from tokio 0.1!"); Ok(()) })); // spawn an `async` block future on the same runtime using `tokio` // 0.2's `spawn`: tokio_02::spawn(async { println!("hello from tokio 0.2!"); }); Ok(()) })) ``` Futures on the compat runtime can use `timer` APIs from both 0.1 and 0.2 versions of `tokio`: ```rust use std::time::{Duration, Instant}; use futures_01::future::lazy; use tokio_compat::prelude::*; tokio_compat::run_03(async { // Wait for a `tokio` 0.1 `Delay`... let when = Instant::now() + Duration::from_millis(10); tokio_01::timer::Delay::new(when) // convert the delay future into a `std::future` that we can `await`. .compat() .await .expect("tokio 0.1 timer should work!"); println!("10 ms have elapsed"); // Wait for a `tokio` 0.2 `Delay`... let when = Instant::now() + Duration::from_millis(20); tokio_02::timer::delay(when).await; println!("20 ms have elapsed"); }); ``` ## Future Work This is just an initial implementation of a `tokio-compat` crate; there are more compatibility layers we'll want to provide before that crate is complete. For example, we should also provide compatibility between `tokio` 0.2's `AsyncRead` and `AsyncWrite` traits and the `futures` 0.1 and `futures` 0.3 versions of those traits. In #1549, @carllerche also suggests that the `compat` crate provide reimplementations of APIs that were removed from `tokio` 0.2 proper, such as the `tcp::Incoming` future. Additionally, there is likely extra work required to get the `tokio-threadpool` 0.1 `blocking` APIs to work on the compat runtime. This will be addressed in a follow-up PR. Fixes: #1605 Fixes: #1552 Refs: #1549 [futures-compat]: https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.19/futures/compat/index.html
114 lines
2.8 KiB
Rust
114 lines
2.8 KiB
Rust
use tokio_executor_01::{self as executor_01, park as park_01};
|
|
use tokio_reactor_01 as reactor_01;
|
|
use tokio_timer_02::{clock as clock_02, timer as timer_02};
|
|
|
|
use std::{
|
|
io, thread,
|
|
time::{Duration, Instant},
|
|
};
|
|
use tokio_02::executor::{current_thread::CurrentThread, park};
|
|
use tokio_02::sync::oneshot;
|
|
|
|
#[derive(Debug)]
|
|
pub(super) struct Background {
|
|
reactor_handle: reactor_01::Handle,
|
|
timer_handle: timer_02::Handle,
|
|
shutdown_tx: Option<oneshot::Sender<()>>,
|
|
thread: Option<thread::JoinHandle<()>>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(super) struct Now<N>(N);
|
|
|
|
#[derive(Debug)]
|
|
struct CompatPark<P>(P);
|
|
|
|
impl Background {
|
|
pub(super) fn spawn(clock: &tokio_02::timer::clock::Clock) -> io::Result<Self> {
|
|
let clock = clock_02::Clock::new_with_now(Now(clock.clone()));
|
|
|
|
let reactor = reactor_01::Reactor::new()?;
|
|
let reactor_handle = reactor.handle();
|
|
|
|
let timer = timer_02::Timer::new_with_now(reactor, clock);
|
|
let timer_handle = timer.handle();
|
|
|
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
|
let shutdown_tx = Some(shutdown_tx);
|
|
|
|
let thread = thread::spawn(move || {
|
|
let mut rt = CurrentThread::new_with_park(CompatPark(timer));
|
|
let _ = rt.block_on(shutdown_rx);
|
|
});
|
|
let thread = Some(thread);
|
|
|
|
Ok(Self {
|
|
reactor_handle,
|
|
timer_handle,
|
|
thread,
|
|
shutdown_tx,
|
|
})
|
|
}
|
|
|
|
pub(super) fn reactor(&self) -> &reactor_01::Handle {
|
|
&self.reactor_handle
|
|
}
|
|
|
|
pub(super) fn timer(&self) -> &timer_02::Handle {
|
|
&self.timer_handle
|
|
}
|
|
}
|
|
|
|
impl Drop for Background {
|
|
fn drop(&mut self) {
|
|
let _ = self.shutdown_tx.take().unwrap().send(());
|
|
let _ = self.thread.take().unwrap().join();
|
|
}
|
|
}
|
|
|
|
pub(super) fn spawn_err(new: tokio_02::executor::SpawnError) -> executor_01::SpawnError {
|
|
match new {
|
|
_ if new.is_shutdown() => executor_01::SpawnError::shutdown(),
|
|
_ if new.is_at_capacity() => executor_01::SpawnError::at_capacity(),
|
|
e => unreachable!("weird spawn error {:?}", e),
|
|
}
|
|
}
|
|
|
|
impl<P> park::Park for CompatPark<P>
|
|
where
|
|
P: park_01::Park,
|
|
{
|
|
type Unpark = CompatPark<P::Unpark>;
|
|
type Error = P::Error;
|
|
|
|
fn unpark(&self) -> Self::Unpark {
|
|
CompatPark(self.0.unpark())
|
|
}
|
|
|
|
#[inline]
|
|
fn park(&mut self) -> Result<(), Self::Error> {
|
|
self.0.park()
|
|
}
|
|
|
|
#[inline]
|
|
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
|
|
self.0.park_timeout(duration)
|
|
}
|
|
}
|
|
|
|
impl<U> park::Unpark for CompatPark<U>
|
|
where
|
|
U: park_01::Unpark,
|
|
{
|
|
#[inline]
|
|
fn unpark(&self) {
|
|
self.0.unpark()
|
|
}
|
|
}
|
|
|
|
impl clock_02::Now for Now<tokio_02::timer::clock::Clock> {
|
|
fn now(&self) -> Instant {
|
|
self.0.now()
|
|
}
|
|
}
|