Files
tokio/tokio-compat/src/runtime/compat.rs
T
Eliza WeismanandGitHub e699d46534 compat: add a compat runtime (#1663)
## 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
2019-11-01 10:35:02 -07:00

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()
}
}