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}