Skip to main content

async_compat/
lib.rs

1//! Compatibility adapter between tokio and futures.
2//!
3//! There are two kinds of compatibility issues between [tokio] and [futures]:
4//!
5//! 1. Tokio's types cannot be used outside tokio context, so any attempt to use them will panic.
6//!     - Solution: If you apply the [`Compat`] adapter to a future, the future will manually
7//!       enter the context of a global tokio runtime. If a runtime is already available via tokio
8//!       thread-locals, then it will be used. Otherwise, a default single-threaded runtime will be
9//!       created on demand. That does *not* mean the future is polled by the tokio runtime - it
10//!       only means the future sets a thread-local variable pointing to the global tokio runtime so
11//!       that tokio's types can be used inside it.
12//! 2. Tokio and futures have similar but different I/O traits `AsyncRead`, `AsyncWrite`,
13//!    `AsyncBufRead`, and `AsyncSeek`.
14//!     - Solution: When the [`Compat`] adapter is applied to an I/O type, it will implement traits
15//!       of the opposite kind. That's how you can use tokio-based types wherever futures-based
16//!       types are expected, and the other way around.
17//!
18//! You can apply the [`Compat`] adapter using the [`Compat::new()`] constructor or using any
19//! method from the [`CompatExt`] trait.
20//!
21//! # The `multi-thread` feature
22//!
23//! Because the default fallback runtime is single-threaded, a task *spawned* within a [`Compat`]
24//! future runs on a current-thread scheduler, where `tokio::task::block_in_place` panics with
25//! `can call blocking only when running on the multi-threaded runtime`. That call is how an
26//! async wrapper around a synchronous library runs a blocking call without stalling the tasks
27//! queued behind it, so a dependency written that way cannot be driven through [`Compat`]
28//! unless a multi-threaded runtime is already ambient.
29//!
30//! Enabling the `multi-thread` feature backs the fallback runtime with a multi-threaded scheduler
31//! instead, so spawned tasks run on real worker threads and `block_in_place` behaves as it would
32//! in any other tokio program. The trade-off is that the runtime starts a worker pool (sized to
33//! the available parallelism) rather than a single thread, so it is off by default.
34//!
35//! The feature affects the process-wide fallback runtime, and cargo features are additive, so
36//! enabling it anywhere in a dependency graph enables it for every [`Compat`] user in that binary.
37//! Nothing that works without the feature stops working with it - a multi-threaded scheduler is
38//! strictly more permissive - but it is worth knowing that the choice is not local to one crate.
39//!
40//! # Configuring the fallback runtime
41//!
42//! Call [`set_fallback_runtime()`] before anything falls back, for example to name its threads
43//! or, with the `multi-thread` feature, to set its number of worker threads.
44//!
45//! # Examples
46//!
47//! This program reads lines from stdin and echoes them into stdout, except it's not going to work:
48//!
49//! ```compile_fail
50//! fn main() -> std::io::Result<()> {
51//!     futures::executor::block_on(async {
52//!         let stdin = tokio::io::stdin();
53//!         let mut stdout = tokio::io::stdout();
54//!
55//!         // The following line will not work for two reasons:
56//!         // 1. Runtime error because stdin and stdout are used outside tokio context.
57//!         // 2. Compilation error due to mismatched `AsyncRead` and `AsyncWrite` traits.
58//!         futures::io::copy(stdin, &mut stdout).await?;
59//!         Ok(())
60//!     })
61//! }
62//! ```
63//!
64//! To get around the compatibility issues, apply the [`Compat`] adapter to `stdin`, `stdout`, and
65//! [`futures::io::copy()`]:
66//!
67//! ```
68//! use async_compat::CompatExt;
69//!
70//! fn main() -> std::io::Result<()> {
71//!     futures::executor::block_on(async {
72//!         let stdin = tokio::io::stdin();
73//!         let mut stdout = tokio::io::stdout();
74//!
75//!         futures::io::copy(stdin.compat(), &mut stdout.compat_mut()).compat().await?;
76//!         Ok(())
77//!     })
78//! }
79//! ```
80//!
81//! It is also possible to apply [`Compat`] to the outer future passed to
82//! [`futures::executor::block_on()`] rather than [`futures::io::copy()`] itself.
83//! When applied to the outer future, individual inner futures don't need the adapter because
84//! they're all now inside tokio context:
85//!
86//! ```no_run
87//! use async_compat::{Compat, CompatExt};
88//!
89//! fn main() -> std::io::Result<()> {
90//!     futures::executor::block_on(Compat::new(async {
91//!         let stdin = tokio::io::stdin();
92//!         let mut stdout = tokio::io::stdout();
93//!
94//!         futures::io::copy(stdin.compat(), &mut stdout.compat_mut()).await?;
95//!         Ok(())
96//!     }))
97//! }
98//! ```
99//!
100//! The compatibility adapter converts between tokio-based and futures-based I/O types in any
101//! direction. Here's how we can write the same program by using futures-based I/O types inside
102//! tokio:
103//!
104//! ```no_run
105//! use async_compat::CompatExt;
106//! use blocking::Unblock;
107//!
108//! #[tokio::main]
109//! async fn main() -> std::io::Result<()> {
110//!     let mut stdin = Unblock::new(std::io::stdin());
111//!     let mut stdout = Unblock::new(std::io::stdout());
112//!
113//!     tokio::io::copy(&mut stdin.compat_mut(), &mut stdout.compat_mut()).await?;
114//!     Ok(())
115//! }
116//! ```
117//!
118//! Finally, we can use any tokio-based crate from any other async runtime.
119//! Here are [reqwest] and [warp] as an example:
120//!
121//! ```no_run
122//! use async_compat::{Compat, CompatExt};
123//! use warp::Filter;
124//!
125//! fn main() {
126//!     futures::executor::block_on(Compat::new(async {
127//!         // Make an HTTP GET request.
128//!         let response = reqwest::get("https://www.rust-lang.org").await.unwrap();
129//!         println!("{}", response.text().await.unwrap());
130//!
131//!         // Start an HTTP server.
132//!         let routes = warp::any().map(|| "Hello from warp!");
133//!         warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
134//!     }))
135//! }
136//! ```
137//!
138//! [blocking]: https://docs.rs/blocking
139//! [futures]: https://docs.rs/futures
140//! [reqwest]: https://docs.rs/reqwest
141//! [tokio]: https://docs.rs/tokio
142//! [warp]: https://docs.rs/warp
143//! [`futures::io::copy()`]: https://docs.rs/futures/0.3/futures/io/fn.copy.html
144//! [`futures::executor::block_on()`]: https://docs.rs/futures/0.3/futures/executor/fn.block_on.html
145
146#![allow(clippy::needless_doctest_main)]
147#![doc(
148    html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
149)]
150#![doc(
151    html_logo_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
152)]
153
154use std::fmt;
155use std::future::Future;
156use std::io;
157use std::pin::Pin;
158use std::task::{Context, Poll};
159#[cfg(not(feature = "multi-thread"))]
160use std::thread;
161
162use futures_core::ready;
163use once_cell::sync::OnceCell;
164use pin_project_lite::pin_project;
165
166/// Applies the [`Compat`] adapter to futures and I/O types.
167pub trait CompatExt {
168    /// Applies the [`Compat`] adapter by value.
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// use async_compat::CompatExt;
174    ///
175    /// let stdout = tokio::io::stdout().compat();
176    /// ```
177    fn compat(self) -> Compat<Self>
178    where
179        Self: Sized;
180
181    /// Applies the [`Compat`] adapter by shared reference.
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// use async_compat::CompatExt;
187    ///
188    /// let original = tokio::io::stdout();
189    /// let stdout = original.compat_ref();
190    /// ```
191    fn compat_ref(&self) -> Compat<&Self>;
192
193    /// Applies the [`Compat`] adapter by mutable reference.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use async_compat::CompatExt;
199    ///
200    /// let mut original = tokio::io::stdout();
201    /// let stdout = original.compat_mut();
202    /// ```
203    fn compat_mut(&mut self) -> Compat<&mut Self>;
204}
205
206impl<T> CompatExt for T {
207    fn compat(self) -> Compat<Self>
208    where
209        Self: Sized,
210    {
211        Compat::new(self)
212    }
213
214    fn compat_ref(&self) -> Compat<&Self> {
215        Compat::new(self)
216    }
217
218    fn compat_mut(&mut self) -> Compat<&mut Self> {
219        Compat::new(self)
220    }
221}
222
223pin_project! {
224    /// Compatibility adapter for futures and I/O types.
225    #[derive(Clone)]
226    pub struct Compat<T> {
227        #[pin]
228        inner: Option<T>,
229        seek_pos: Option<io::SeekFrom>,
230    }
231
232    impl<T> PinnedDrop for Compat<T> {
233        fn drop(this: Pin<&mut Self>) {
234            if this.inner.is_some() {
235                // If the inner future wasn't moved out using into_inner,
236                // enter the tokio context while the inner value is dropped.
237                let _guard = get_runtime_handle().enter();
238                this.project().inner.set(None);
239            }
240        }
241    }
242}
243
244impl<T> Compat<T> {
245    /// Applies the compatibility adapter to a future or an I/O type.
246    ///
247    /// # Examples
248    ///
249    /// Apply it to a future:
250    ///
251    /// ```
252    /// use async_compat::Compat;
253    /// use std::time::Duration;
254    ///
255    /// futures::executor::block_on(Compat::new(async {
256    ///     // We can use tokio's timers because we're inside tokio context.
257    ///     tokio::time::sleep(Duration::from_secs(1)).await;
258    /// }));
259    /// ```
260    ///
261    /// Apply it to an I/O type:
262    ///
263    /// ```
264    /// use async_compat::{Compat, CompatExt};
265    /// use futures::prelude::*;
266    ///
267    /// # fn main() -> std::io::Result<()> {
268    /// futures::executor::block_on(Compat::new(async {
269    ///     // The `write_all` method comes from `futures::io::AsyncWriteExt`.
270    ///     Compat::new(tokio::io::stdout()).write_all(b"hello\n").await?;
271    ///     Ok(())
272    /// }))
273    /// # }
274    /// ```
275    pub fn new(t: T) -> Compat<T> {
276        Compat {
277            inner: Some(t),
278            seek_pos: None,
279        }
280    }
281
282    /// Gets a shared reference to the inner value.
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use async_compat::Compat;
288    /// use tokio::net::UdpSocket;
289    ///
290    /// # fn main() -> std::io::Result<()> {
291    /// futures::executor::block_on(Compat::new(async {
292    ///     let socket = Compat::new(UdpSocket::bind("127.0.0.1:0").await?);
293    ///     let addr = socket.get_ref().local_addr()?;
294    ///     Ok(())
295    /// }))
296    /// # }
297    /// ```
298    pub fn get_ref(&self) -> &T {
299        self.inner
300            .as_ref()
301            .expect("inner is only None when Compat is about to drop")
302    }
303
304    /// Gets a mutable reference to the inner value.
305    ///
306    /// # Examples
307    ///
308    /// ```no_run
309    /// use async_compat::Compat;
310    /// use tokio::net::TcpListener;
311    ///
312    /// # fn main() -> std::io::Result<()> {
313    /// futures::executor::block_on(Compat::new(async {
314    ///     let mut listener = Compat::new(TcpListener::bind("127.0.0.1:0").await?);
315    ///     let (stream, addr) = listener.get_mut().accept().await?;
316    ///     let stream = Compat::new(stream);
317    ///     Ok(())
318    /// }))
319    /// # }
320    /// ```
321    pub fn get_mut(&mut self) -> &mut T {
322        self.inner
323            .as_mut()
324            .expect("inner is only None when Compat is about to drop")
325    }
326
327    fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
328        self.project()
329            .inner
330            .as_pin_mut()
331            .expect("inner is only None when Compat is about to drop")
332    }
333
334    /// Unwraps the compatibility adapter.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use async_compat::Compat;
340    ///
341    /// let stdout = Compat::new(tokio::io::stdout());
342    /// let original = stdout.into_inner();
343    /// ```
344    pub fn into_inner(mut self) -> T {
345        self.inner
346            .take()
347            .expect("inner is only None when Compat is about to drop")
348    }
349}
350
351impl<T: Future> Future for Compat<T> {
352    type Output = T::Output;
353
354    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
355        let _guard = get_runtime_handle().enter();
356        self.get_pin_mut().poll(cx)
357    }
358}
359
360impl<T: tokio::io::AsyncRead> futures_io::AsyncRead for Compat<T> {
361    fn poll_read(
362        self: Pin<&mut Self>,
363        cx: &mut Context<'_>,
364        buf: &mut [u8],
365    ) -> Poll<io::Result<usize>> {
366        let mut buf = tokio::io::ReadBuf::new(buf);
367        ready!(self.get_pin_mut().poll_read(cx, &mut buf))?;
368        Poll::Ready(Ok(buf.filled().len()))
369    }
370}
371
372impl<T: futures_io::AsyncRead> tokio::io::AsyncRead for Compat<T> {
373    fn poll_read(
374        self: Pin<&mut Self>,
375        cx: &mut Context<'_>,
376        buf: &mut tokio::io::ReadBuf<'_>,
377    ) -> Poll<io::Result<()>> {
378        let unfilled = buf.initialize_unfilled();
379        let poll = self.get_pin_mut().poll_read(cx, unfilled);
380        if let Poll::Ready(Ok(num)) = &poll {
381            buf.advance(*num);
382        }
383        poll.map_ok(|_| ())
384    }
385}
386
387impl<T: tokio::io::AsyncBufRead> futures_io::AsyncBufRead for Compat<T> {
388    fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
389        self.get_pin_mut().poll_fill_buf(cx)
390    }
391
392    fn consume(self: Pin<&mut Self>, amt: usize) {
393        self.get_pin_mut().consume(amt)
394    }
395}
396
397impl<T: futures_io::AsyncBufRead> tokio::io::AsyncBufRead for Compat<T> {
398    fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
399        self.get_pin_mut().poll_fill_buf(cx)
400    }
401
402    fn consume(self: Pin<&mut Self>, amt: usize) {
403        self.get_pin_mut().consume(amt)
404    }
405}
406
407impl<T: tokio::io::AsyncWrite> futures_io::AsyncWrite for Compat<T> {
408    fn poll_write(
409        self: Pin<&mut Self>,
410        cx: &mut Context<'_>,
411        buf: &[u8],
412    ) -> Poll<io::Result<usize>> {
413        self.get_pin_mut().poll_write(cx, buf)
414    }
415
416    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
417        self.get_pin_mut().poll_flush(cx)
418    }
419
420    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
421        self.get_pin_mut().poll_shutdown(cx)
422    }
423}
424
425impl<T: futures_io::AsyncWrite> tokio::io::AsyncWrite for Compat<T> {
426    fn poll_write(
427        self: Pin<&mut Self>,
428        cx: &mut Context<'_>,
429        buf: &[u8],
430    ) -> Poll<io::Result<usize>> {
431        self.get_pin_mut().poll_write(cx, buf)
432    }
433
434    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
435        self.get_pin_mut().poll_flush(cx)
436    }
437
438    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
439        self.get_pin_mut().poll_close(cx)
440    }
441}
442
443impl<T: tokio::io::AsyncSeek> futures_io::AsyncSeek for Compat<T> {
444    fn poll_seek(
445        mut self: Pin<&mut Self>,
446        cx: &mut Context,
447        pos: io::SeekFrom,
448    ) -> Poll<io::Result<u64>> {
449        if self.seek_pos != Some(pos) {
450            self.as_mut().get_pin_mut().start_seek(pos)?;
451            *self.as_mut().project().seek_pos = Some(pos);
452        }
453        let res = ready!(self.as_mut().get_pin_mut().poll_complete(cx));
454        *self.as_mut().project().seek_pos = None;
455        Poll::Ready(res)
456    }
457}
458
459impl<T: futures_io::AsyncSeek> tokio::io::AsyncSeek for Compat<T> {
460    fn start_seek(mut self: Pin<&mut Self>, pos: io::SeekFrom) -> io::Result<()> {
461        *self.as_mut().project().seek_pos = Some(pos);
462        Ok(())
463    }
464
465    fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<u64>> {
466        let pos = match self.seek_pos {
467            None => {
468                // tokio 1.x AsyncSeek recommends calling poll_complete before start_seek.
469                // We don't have to guarantee that the value returned by
470                // poll_complete called without start_seek is correct,
471                // so we'll return 0.
472                return Poll::Ready(Ok(0));
473            }
474            Some(pos) => pos,
475        };
476        let res = ready!(self.as_mut().get_pin_mut().poll_seek(cx, pos));
477        *self.as_mut().project().seek_pos = None;
478        Poll::Ready(res)
479    }
480}
481
482/// Configures the runtime that [`Compat`] uses when it is polled outside a tokio context.
483///
484/// The runtime is built right away with the default's scheduler (current-thread, or
485/// multi-threaded with the `multi-thread` feature) and [`enable_all()`], then `configure`
486/// applies. Call it before anything falls back; it is process-wide, so only applications
487/// should call it.
488///
489/// # Errors
490///
491/// Fails if a fallback runtime already exists, or if the runtime or the thread that drives a
492/// current-thread one cannot be created; the default runtime is used then.
493///
494/// # Examples
495///
496/// ```
497/// async_compat::set_fallback_runtime(|builder| {
498///     builder.worker_threads(2).thread_name("my-app-tokio");
499/// })
500/// .unwrap();
501/// ```
502///
503/// [`enable_all()`]: tokio::runtime::Builder::enable_all
504pub fn set_fallback_runtime(
505    configure: impl FnOnce(&mut tokio::runtime::Builder),
506) -> Result<(), SetFallbackRuntimeError> {
507    let mut configure = Some(configure);
508    // Nothing is built if the slot is already taken, and a failure leaves it free.
509    TOKIO1.get_or_try_init(|| new_fallback_runtime(configure.take().unwrap()))?;
510    match configure {
511        None => Ok(()),
512        Some(_) => Err(SetFallbackRuntimeError::AlreadySet),
513    }
514}
515
516/// The error returned by [`set_fallback_runtime()`].
517#[derive(Debug)]
518#[non_exhaustive]
519pub enum SetFallbackRuntimeError {
520    /// A fallback runtime has already been set or created.
521    AlreadySet,
522    /// The runtime could not be built.
523    BuildFailed(io::Error),
524    /// The thread that drives a current-thread runtime could not be spawned.
525    SpawnFailed(io::Error),
526}
527
528impl fmt::Display for SetFallbackRuntimeError {
529    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530        f.write_str(match self {
531            Self::AlreadySet => "a fallback runtime already exists",
532            Self::BuildFailed(_) => "cannot build the fallback runtime",
533            Self::SpawnFailed(_) => "cannot spawn the thread that drives the fallback runtime",
534        })
535    }
536}
537
538impl std::error::Error for SetFallbackRuntimeError {
539    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
540        match self {
541            Self::AlreadySet => None,
542            Self::BuildFailed(err) | Self::SpawnFailed(err) => Some(err),
543        }
544    }
545}
546
547fn get_runtime_handle() -> tokio::runtime::Handle {
548    tokio::runtime::Handle::try_current().unwrap_or_else(|_| fallback_runtime().handle().clone())
549}
550
551static TOKIO1: OnceCell<tokio::runtime::Runtime> = OnceCell::new();
552
553fn fallback_runtime() -> &'static tokio::runtime::Runtime {
554    TOKIO1.get_or_init(|| new_fallback_runtime(|_| {}).expect("cannot start tokio-1 runtime"))
555}
556
557/// Builds a fallback runtime and, for a current-thread one, spawns the thread
558/// that drives it. Nothing is published until both have succeeded.
559fn new_fallback_runtime(
560    configure: impl FnOnce(&mut tokio::runtime::Builder),
561) -> Result<tokio::runtime::Runtime, SetFallbackRuntimeError> {
562    #[cfg(not(feature = "multi-thread"))]
563    let mut builder = tokio::runtime::Builder::new_current_thread();
564    // A multi-threaded scheduler drives its own workers, reactor and timer, so it
565    // needs no thread to hold it open. Spawned tasks run on real worker threads,
566    // which is what makes `tokio::task::block_in_place` usable inside them.
567    #[cfg(feature = "multi-thread")]
568    let mut builder = {
569        let mut builder = tokio::runtime::Builder::new_multi_thread();
570        builder.thread_name("async-compat/tokio-1");
571        builder
572    };
573    builder.enable_all();
574    configure(&mut builder);
575    let rt = builder
576        .build()
577        .map_err(SetFallbackRuntimeError::BuildFailed)?;
578    #[cfg(not(feature = "multi-thread"))]
579    if let Err(err) = drive_fallback_runtime() {
580        // Dropping a runtime inside async code panics; this does not.
581        rt.shutdown_background();
582        return Err(SetFallbackRuntimeError::SpawnFailed(err));
583    }
584    Ok(rt)
585}
586
587/// A current-thread scheduler parks unless something drives it, so a dedicated
588/// thread holds it open on a future that never completes. Tasks spawned into it
589/// therefore run on that one thread.
590#[cfg(not(feature = "multi-thread"))]
591fn drive_fallback_runtime() -> io::Result<()> {
592    thread::Builder::new()
593        .name("async-compat/tokio-1".into())
594        .spawn(|| fallback_runtime().block_on(Pending))?;
595    Ok(())
596}
597
598#[cfg(not(feature = "multi-thread"))]
599struct Pending;
600
601#[cfg(not(feature = "multi-thread"))]
602impl Future for Pending {
603    type Output = ();
604
605    fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
606        Poll::Pending
607    }
608}
609
610#[cfg(test)]
611mod tests {
612    use crate::{CompatExt, TOKIO1};
613
614    #[test]
615    fn fallback_runtime_is_created_if_and_only_if_outside_tokio_context() {
616        // Use compat inside of a tokio context.
617        tokio::runtime::Builder::new_multi_thread()
618            .enable_all()
619            .build()
620            .unwrap()
621            .block_on(use_tokio().compat());
622
623        // We didn't need to create the fallback runtime, because we used compat
624        // inside of an existing tokio context.
625        assert!(TOKIO1.get().is_none());
626
627        // Use compat outside of a tokio context.
628        futures::executor::block_on(use_tokio().compat());
629
630        // We must have created the fallback runtime, because we used compat
631        // outside of a tokio context.
632        assert!(TOKIO1.get().is_some());
633    }
634
635    async fn use_tokio() {
636        tokio::time::sleep(std::time::Duration::from_micros(1)).await
637    }
638}