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 new 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 fallback runtime is single-threaded, a task *spawned* from inside 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//! # Examples
41//!
42//! This program reads lines from stdin and echoes them into stdout, except it's not going to work:
43//!
44//! ```compile_fail
45//! fn main() -> std::io::Result<()> {
46//! futures::executor::block_on(async {
47//! let stdin = tokio::io::stdin();
48//! let mut stdout = tokio::io::stdout();
49//!
50//! // The following line will not work for two reasons:
51//! // 1. Runtime error because stdin and stdout are used outside tokio context.
52//! // 2. Compilation error due to mismatched `AsyncRead` and `AsyncWrite` traits.
53//! futures::io::copy(stdin, &mut stdout).await?;
54//! Ok(())
55//! })
56//! }
57//! ```
58//!
59//! To get around the compatibility issues, apply the [`Compat`] adapter to `stdin`, `stdout`, and
60//! [`futures::io::copy()`]:
61//!
62//! ```
63//! use async_compat::CompatExt;
64//!
65//! fn main() -> std::io::Result<()> {
66//! futures::executor::block_on(async {
67//! let stdin = tokio::io::stdin();
68//! let mut stdout = tokio::io::stdout();
69//!
70//! futures::io::copy(stdin.compat(), &mut stdout.compat_mut()).compat().await?;
71//! Ok(())
72//! })
73//! }
74//! ```
75//!
76//! It is also possible to apply [`Compat`] to the outer future passed to
77//! [`futures::executor::block_on()`] rather than [`futures::io::copy()`] itself.
78//! When applied to the outer future, individual inner futures don't need the adapter because
79//! they're all now inside tokio context:
80//!
81//! ```no_run
82//! use async_compat::{Compat, CompatExt};
83//!
84//! fn main() -> std::io::Result<()> {
85//! futures::executor::block_on(Compat::new(async {
86//! let stdin = tokio::io::stdin();
87//! let mut stdout = tokio::io::stdout();
88//!
89//! futures::io::copy(stdin.compat(), &mut stdout.compat_mut()).await?;
90//! Ok(())
91//! }))
92//! }
93//! ```
94//!
95//! The compatibility adapter converts between tokio-based and futures-based I/O types in any
96//! direction. Here's how we can write the same program by using futures-based I/O types inside
97//! tokio:
98//!
99//! ```no_run
100//! use async_compat::CompatExt;
101//! use blocking::Unblock;
102//!
103//! #[tokio::main]
104//! async fn main() -> std::io::Result<()> {
105//! let mut stdin = Unblock::new(std::io::stdin());
106//! let mut stdout = Unblock::new(std::io::stdout());
107//!
108//! tokio::io::copy(&mut stdin.compat_mut(), &mut stdout.compat_mut()).await?;
109//! Ok(())
110//! }
111//! ```
112//!
113//! Finally, we can use any tokio-based crate from any other async runtime.
114//! Here are [reqwest] and [warp] as an example:
115//!
116//! ```no_run
117//! use async_compat::{Compat, CompatExt};
118//! use warp::Filter;
119//!
120//! fn main() {
121//! futures::executor::block_on(Compat::new(async {
122//! // Make an HTTP GET request.
123//! let response = reqwest::get("https://www.rust-lang.org").await.unwrap();
124//! println!("{}", response.text().await.unwrap());
125//!
126//! // Start an HTTP server.
127//! let routes = warp::any().map(|| "Hello from warp!");
128//! warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
129//! }))
130//! }
131//! ```
132//!
133//! [blocking]: https://docs.rs/blocking
134//! [futures]: https://docs.rs/futures
135//! [reqwest]: https://docs.rs/reqwest
136//! [tokio]: https://docs.rs/tokio
137//! [warp]: https://docs.rs/warp
138//! [`futures::io::copy()`]: https://docs.rs/futures/0.3/futures/io/fn.copy.html
139//! [`futures::executor::block_on()`]: https://docs.rs/futures/0.3/futures/executor/fn.block_on.html
140
141#![allow(clippy::needless_doctest_main)]
142#![doc(
143 html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
144)]
145#![doc(
146 html_logo_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
147)]
148
149use std::future::Future;
150use std::io;
151use std::pin::Pin;
152use std::task::{Context, Poll};
153#[cfg(not(feature = "multi-thread"))]
154use std::thread;
155
156use futures_core::ready;
157use once_cell::sync::Lazy;
158use pin_project_lite::pin_project;
159
160/// Applies the [`Compat`] adapter to futures and I/O types.
161pub trait CompatExt {
162 /// Applies the [`Compat`] adapter by value.
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// use async_compat::CompatExt;
168 ///
169 /// let stdout = tokio::io::stdout().compat();
170 /// ```
171 fn compat(self) -> Compat<Self>
172 where
173 Self: Sized;
174
175 /// Applies the [`Compat`] adapter by shared reference.
176 ///
177 /// # Examples
178 ///
179 /// ```
180 /// use async_compat::CompatExt;
181 ///
182 /// let original = tokio::io::stdout();
183 /// let stdout = original.compat_ref();
184 /// ```
185 fn compat_ref(&self) -> Compat<&Self>;
186
187 /// Applies the [`Compat`] adapter by mutable reference.
188 ///
189 /// # Examples
190 ///
191 /// ```
192 /// use async_compat::CompatExt;
193 ///
194 /// let mut original = tokio::io::stdout();
195 /// let stdout = original.compat_mut();
196 /// ```
197 fn compat_mut(&mut self) -> Compat<&mut Self>;
198}
199
200impl<T> CompatExt for T {
201 fn compat(self) -> Compat<Self>
202 where
203 Self: Sized,
204 {
205 Compat::new(self)
206 }
207
208 fn compat_ref(&self) -> Compat<&Self> {
209 Compat::new(self)
210 }
211
212 fn compat_mut(&mut self) -> Compat<&mut Self> {
213 Compat::new(self)
214 }
215}
216
217pin_project! {
218 /// Compatibility adapter for futures and I/O types.
219 #[derive(Clone)]
220 pub struct Compat<T> {
221 #[pin]
222 inner: Option<T>,
223 seek_pos: Option<io::SeekFrom>,
224 }
225
226 impl<T> PinnedDrop for Compat<T> {
227 fn drop(this: Pin<&mut Self>) {
228 if this.inner.is_some() {
229 // If the inner future wasn't moved out using into_inner,
230 // enter the tokio context while the inner value is dropped.
231 let _guard = get_runtime_handle().enter();
232 this.project().inner.set(None);
233 }
234 }
235 }
236}
237
238impl<T> Compat<T> {
239 /// Applies the compatibility adapter to a future or an I/O type.
240 ///
241 /// # Examples
242 ///
243 /// Apply it to a future:
244 ///
245 /// ```
246 /// use async_compat::Compat;
247 /// use std::time::Duration;
248 ///
249 /// futures::executor::block_on(Compat::new(async {
250 /// // We can use tokio's timers because we're inside tokio context.
251 /// tokio::time::sleep(Duration::from_secs(1)).await;
252 /// }));
253 /// ```
254 ///
255 /// Apply it to an I/O type:
256 ///
257 /// ```
258 /// use async_compat::{Compat, CompatExt};
259 /// use futures::prelude::*;
260 ///
261 /// # fn main() -> std::io::Result<()> {
262 /// futures::executor::block_on(Compat::new(async {
263 /// // The `write_all` method comes from `futures::io::AsyncWriteExt`.
264 /// Compat::new(tokio::io::stdout()).write_all(b"hello\n").await?;
265 /// Ok(())
266 /// }))
267 /// # }
268 /// ```
269 pub fn new(t: T) -> Compat<T> {
270 Compat {
271 inner: Some(t),
272 seek_pos: None,
273 }
274 }
275
276 /// Gets a shared reference to the inner value.
277 ///
278 /// # Examples
279 ///
280 /// ```
281 /// use async_compat::Compat;
282 /// use tokio::net::UdpSocket;
283 ///
284 /// # fn main() -> std::io::Result<()> {
285 /// futures::executor::block_on(Compat::new(async {
286 /// let socket = Compat::new(UdpSocket::bind("127.0.0.1:0").await?);
287 /// let addr = socket.get_ref().local_addr()?;
288 /// Ok(())
289 /// }))
290 /// # }
291 /// ```
292 pub fn get_ref(&self) -> &T {
293 self.inner
294 .as_ref()
295 .expect("inner is only None when Compat is about to drop")
296 }
297
298 /// Gets a mutable reference to the inner value.
299 ///
300 /// # Examples
301 ///
302 /// ```no_run
303 /// use async_compat::Compat;
304 /// use tokio::net::TcpListener;
305 ///
306 /// # fn main() -> std::io::Result<()> {
307 /// futures::executor::block_on(Compat::new(async {
308 /// let mut listener = Compat::new(TcpListener::bind("127.0.0.1:0").await?);
309 /// let (stream, addr) = listener.get_mut().accept().await?;
310 /// let stream = Compat::new(stream);
311 /// Ok(())
312 /// }))
313 /// # }
314 /// ```
315 pub fn get_mut(&mut self) -> &mut T {
316 self.inner
317 .as_mut()
318 .expect("inner is only None when Compat is about to drop")
319 }
320
321 fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
322 self.project()
323 .inner
324 .as_pin_mut()
325 .expect("inner is only None when Compat is about to drop")
326 }
327
328 /// Unwraps the compatibility adapter.
329 ///
330 /// # Examples
331 ///
332 /// ```
333 /// use async_compat::Compat;
334 ///
335 /// let stdout = Compat::new(tokio::io::stdout());
336 /// let original = stdout.into_inner();
337 /// ```
338 pub fn into_inner(mut self) -> T {
339 self.inner
340 .take()
341 .expect("inner is only None when Compat is about to drop")
342 }
343}
344
345impl<T: Future> Future for Compat<T> {
346 type Output = T::Output;
347
348 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
349 let _guard = get_runtime_handle().enter();
350 self.get_pin_mut().poll(cx)
351 }
352}
353
354impl<T: tokio::io::AsyncRead> futures_io::AsyncRead for Compat<T> {
355 fn poll_read(
356 self: Pin<&mut Self>,
357 cx: &mut Context<'_>,
358 buf: &mut [u8],
359 ) -> Poll<io::Result<usize>> {
360 let mut buf = tokio::io::ReadBuf::new(buf);
361 ready!(self.get_pin_mut().poll_read(cx, &mut buf))?;
362 Poll::Ready(Ok(buf.filled().len()))
363 }
364}
365
366impl<T: futures_io::AsyncRead> tokio::io::AsyncRead for Compat<T> {
367 fn poll_read(
368 self: Pin<&mut Self>,
369 cx: &mut Context<'_>,
370 buf: &mut tokio::io::ReadBuf<'_>,
371 ) -> Poll<io::Result<()>> {
372 let unfilled = buf.initialize_unfilled();
373 let poll = self.get_pin_mut().poll_read(cx, unfilled);
374 if let Poll::Ready(Ok(num)) = &poll {
375 buf.advance(*num);
376 }
377 poll.map_ok(|_| ())
378 }
379}
380
381impl<T: tokio::io::AsyncBufRead> futures_io::AsyncBufRead for Compat<T> {
382 fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
383 self.get_pin_mut().poll_fill_buf(cx)
384 }
385
386 fn consume(self: Pin<&mut Self>, amt: usize) {
387 self.get_pin_mut().consume(amt)
388 }
389}
390
391impl<T: futures_io::AsyncBufRead> tokio::io::AsyncBufRead for Compat<T> {
392 fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
393 self.get_pin_mut().poll_fill_buf(cx)
394 }
395
396 fn consume(self: Pin<&mut Self>, amt: usize) {
397 self.get_pin_mut().consume(amt)
398 }
399}
400
401impl<T: tokio::io::AsyncWrite> futures_io::AsyncWrite for Compat<T> {
402 fn poll_write(
403 self: Pin<&mut Self>,
404 cx: &mut Context<'_>,
405 buf: &[u8],
406 ) -> Poll<io::Result<usize>> {
407 self.get_pin_mut().poll_write(cx, buf)
408 }
409
410 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
411 self.get_pin_mut().poll_flush(cx)
412 }
413
414 fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
415 self.get_pin_mut().poll_shutdown(cx)
416 }
417}
418
419impl<T: futures_io::AsyncWrite> tokio::io::AsyncWrite for Compat<T> {
420 fn poll_write(
421 self: Pin<&mut Self>,
422 cx: &mut Context<'_>,
423 buf: &[u8],
424 ) -> Poll<io::Result<usize>> {
425 self.get_pin_mut().poll_write(cx, buf)
426 }
427
428 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
429 self.get_pin_mut().poll_flush(cx)
430 }
431
432 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
433 self.get_pin_mut().poll_close(cx)
434 }
435}
436
437impl<T: tokio::io::AsyncSeek> futures_io::AsyncSeek for Compat<T> {
438 fn poll_seek(
439 mut self: Pin<&mut Self>,
440 cx: &mut Context,
441 pos: io::SeekFrom,
442 ) -> Poll<io::Result<u64>> {
443 if self.seek_pos != Some(pos) {
444 self.as_mut().get_pin_mut().start_seek(pos)?;
445 *self.as_mut().project().seek_pos = Some(pos);
446 }
447 let res = ready!(self.as_mut().get_pin_mut().poll_complete(cx));
448 *self.as_mut().project().seek_pos = None;
449 Poll::Ready(res)
450 }
451}
452
453impl<T: futures_io::AsyncSeek> tokio::io::AsyncSeek for Compat<T> {
454 fn start_seek(mut self: Pin<&mut Self>, pos: io::SeekFrom) -> io::Result<()> {
455 *self.as_mut().project().seek_pos = Some(pos);
456 Ok(())
457 }
458
459 fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<u64>> {
460 let pos = match self.seek_pos {
461 None => {
462 // tokio 1.x AsyncSeek recommends calling poll_complete before start_seek.
463 // We don't have to guarantee that the value returned by
464 // poll_complete called without start_seek is correct,
465 // so we'll return 0.
466 return Poll::Ready(Ok(0));
467 }
468 Some(pos) => pos,
469 };
470 let res = ready!(self.as_mut().get_pin_mut().poll_seek(cx, pos));
471 *self.as_mut().project().seek_pos = None;
472 Poll::Ready(res)
473 }
474}
475
476fn get_runtime_handle() -> tokio::runtime::Handle {
477 tokio::runtime::Handle::try_current().unwrap_or_else(|_| TOKIO1.handle().clone())
478}
479
480/// A current-thread scheduler parks unless something drives it, so a dedicated
481/// thread holds it open on a future that never completes. Tasks spawned into it
482/// therefore run on that one thread.
483#[cfg(not(feature = "multi-thread"))]
484static TOKIO1: Lazy<tokio::runtime::Runtime> = Lazy::new(|| {
485 thread::Builder::new()
486 .name("async-compat/tokio-1".into())
487 .spawn(|| TOKIO1.block_on(Pending))
488 .unwrap();
489 tokio::runtime::Builder::new_current_thread()
490 .enable_all()
491 .build()
492 .expect("cannot start tokio-1 runtime")
493});
494
495/// A multi-threaded scheduler drives its own workers, reactor and timer, so it
496/// needs no thread to hold it open. Spawned tasks run on real worker threads,
497/// which is what makes [`tokio::task::block_in_place`] usable inside them.
498#[cfg(feature = "multi-thread")]
499static TOKIO1: Lazy<tokio::runtime::Runtime> = Lazy::new(|| {
500 tokio::runtime::Builder::new_multi_thread()
501 .enable_all()
502 .thread_name("async-compat/tokio-1")
503 .build()
504 .expect("cannot start tokio-1 runtime")
505});
506
507#[cfg(not(feature = "multi-thread"))]
508struct Pending;
509
510#[cfg(not(feature = "multi-thread"))]
511impl Future for Pending {
512 type Output = ();
513
514 fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
515 Poll::Pending
516 }
517}
518
519#[cfg(test)]
520mod tests {
521 use super::Lazy;
522 use crate::{CompatExt, TOKIO1};
523
524 #[test]
525 fn fallback_runtime_is_created_if_and_only_if_outside_tokio_context() {
526 // Use compat inside of a tokio context.
527 tokio::runtime::Builder::new_multi_thread()
528 .enable_all()
529 .build()
530 .unwrap()
531 .block_on(use_tokio().compat());
532
533 // We didn't need to create the fallback runtime, because we used compat
534 // inside of an existing tokio context.
535 assert!(Lazy::get(&TOKIO1).is_none());
536
537 // Use compat outside of a tokio context.
538 futures::executor::block_on(use_tokio().compat());
539
540 // We must have created the fallback runtime, because we used compat
541 // outside of a tokio context.
542 assert!(Lazy::get(&TOKIO1).is_some());
543 }
544
545 async fn use_tokio() {
546 tokio::time::sleep(std::time::Duration::from_micros(1)).await
547 }
548}