Skip to main content

blocking/
lib.rs

1//! A thread pool for isolating blocking I/O in async programs.
2//!
3//! Sometimes there's no way to avoid blocking I/O. Consider files or stdin, which have weak async
4//! support on modern operating systems. While [IOCP], [AIO], and [io_uring] are possible
5//! solutions, they're not always available or ideal.
6//!
7//! Since blocking is not allowed inside futures, we must move blocking I/O onto a special thread
8//! pool provided by this crate. The pool dynamically spawns and stops threads depending on the
9//! current number of running I/O jobs.
10//!
11//! Note that there is a limit on the number of active threads. Once that limit is hit, a running
12//! job has to finish before others get a chance to run. When a thread is idle, it waits for the
13//! next job or shuts down after a certain timeout.
14//!
15//! The default number of threads (set to 500) can be altered by setting `BLOCKING_MAX_THREADS` environment
16//! variable with value between 1 and 10000. This can also be set, at runtime, via the
17//! [`set_max_blocking_threads`] function.
18//!
19//! [IOCP]: https://en.wikipedia.org/wiki/Input/output_completion_port
20//! [AIO]: http://man7.org/linux/man-pages/man2/io_submit.2.html
21//! [io_uring]: https://lwn.net/Articles/776703
22//!
23//! # Examples
24//!
25//! Read the contents of a file:
26//!
27//! ```no_run
28//! use blocking::unblock;
29//! use std::fs;
30//!
31//! # futures_lite::future::block_on(async {
32//! let contents = unblock(|| fs::read_to_string("file.txt")).await?;
33//! println!("{}", contents);
34//! # std::io::Result::Ok(()) });
35//! ```
36//!
37//! Read a file and pipe its contents to stdout:
38//!
39//! ```no_run
40//! use blocking::{unblock, Unblock};
41//! use futures_lite::io;
42//! use std::fs::File;
43//!
44//! # futures_lite::future::block_on(async {
45//! let input = unblock(|| File::open("file.txt")).await?;
46//! let input = Unblock::new(input);
47//! let mut output = Unblock::new(std::io::stdout());
48//!
49//! io::copy(input, &mut output).await?;
50//! # std::io::Result::Ok(()) });
51//! ```
52//!
53//! Iterate over the contents of a directory:
54//!
55//! ```no_run
56//! use blocking::Unblock;
57//! use futures_lite::prelude::*;
58//! use std::fs;
59//!
60//! # futures_lite::future::block_on(async {
61//! let mut dir = Unblock::new(fs::read_dir(".")?);
62//! while let Some(item) = dir.next().await {
63//!     println!("{}", item?.file_name().to_string_lossy());
64//! }
65//! # std::io::Result::Ok(()) });
66//! ```
67//!
68//! Spawn a process:
69//!
70//! ```no_run
71//! use blocking::unblock;
72//! use std::process::Command;
73//!
74//! # futures_lite::future::block_on(async {
75//! let out = unblock(|| Command::new("dir").output()).await?;
76//! # std::io::Result::Ok(()) });
77//! ```
78
79#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
80#![forbid(unsafe_code)]
81#![doc(
82    html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
83)]
84#![doc(
85    html_logo_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
86)]
87
88use std::any::Any;
89use std::collections::VecDeque;
90use std::fmt;
91use std::io::{self, Read, Seek, SeekFrom, Write};
92use std::num::NonZeroUsize;
93use std::panic;
94use std::pin::Pin;
95use std::sync::atomic::{AtomicUsize, Ordering};
96use std::sync::{Condvar, Mutex, MutexGuard, PoisonError};
97use std::task::{Context, Poll};
98use std::thread;
99use std::time::Duration;
100
101#[cfg(not(target_family = "wasm"))]
102use std::env;
103
104use async_channel::{bounded, Receiver};
105use async_task::Runnable;
106use futures_io::{AsyncRead, AsyncSeek, AsyncWrite};
107use futures_lite::{
108    future::{self, Future},
109    ready,
110    stream::Stream,
111};
112use piper::{pipe, Reader, Writer};
113
114#[doc(no_inline)]
115pub use async_task::Task;
116
117#[cfg(test)]
118static SPAWN_FAILURES: AtomicUsize = AtomicUsize::new(0);
119
120fn consume_spawn_failure() -> bool {
121    #[cfg(test)]
122    {
123        let mut current = SPAWN_FAILURES.load(Ordering::SeqCst);
124        while current > 0 {
125            match SPAWN_FAILURES.compare_exchange(
126                current,
127                current - 1,
128                Ordering::SeqCst,
129                Ordering::SeqCst,
130            ) {
131                Ok(_) => return true,
132                Err(observed) => current = observed,
133            }
134        }
135    }
136    false
137}
138
139/// Default value for max threads that Executor can grow to
140#[cfg(not(target_family = "wasm"))]
141const DEFAULT_MAX_THREADS: NonZeroUsize = {
142    if let Some(size) = NonZeroUsize::new(500) {
143        size
144    } else {
145        panic!("DEFAULT_MAX_THREADS is non-zero");
146    }
147};
148
149/// Minimum value for max threads config
150#[cfg(not(target_family = "wasm"))]
151const MIN_MAX_THREADS: usize = 1;
152
153/// Maximum value for max threads config
154#[cfg(not(target_family = "wasm"))]
155const MAX_MAX_THREADS: usize = 10000;
156
157/// Env variable that allows to override default value for max threads.
158#[cfg(not(target_family = "wasm"))]
159const MAX_THREADS_ENV: &str = "BLOCKING_MAX_THREADS";
160
161/// Set the maximum number of threads used by the backing thread pool.
162///
163/// # Example
164///
165/// ```no_run
166/// use blocking::unblock;
167/// use std::fs::{read_dir, File};
168/// use std::io::prelude::*;
169/// # use std::num::NonZeroUsize;
170///
171/// blocking::set_max_blocking_threads(NonZeroUsize::new(100).unwrap());
172///
173/// # fn test() -> std::io::Result<()> {
174/// let mut files = Vec::new();
175/// for entry in read_dir("/path/to/large/directory").unwrap() {
176///     files.push(unblock(move || -> std::io::Result<String> {
177///         let mut contents = String::new();
178///         let mut file = File::open(entry?.path())?;
179///         file.read_to_string(&mut contents)?;
180///         Ok(contents)
181///     }));
182/// }
183/// # Ok(())
184/// # }
185/// ```
186pub fn set_max_blocking_threads(threads: NonZeroUsize) {
187    let executor = Executor::get();
188    let mut inner = executor
189        .inner
190        .lock()
191        .unwrap_or_else(PoisonError::into_inner);
192    let old_limit = inner.thread_limit;
193    inner.thread_limit = Some(threads);
194    if let Some(old_limit) = old_limit {
195        // If the limit has decreased, wake up all threads to terminate those over
196        // the new limit.
197        if old_limit > threads {
198            executor.cvar.notify_all();
199        }
200    }
201}
202
203/// The blocking executor.
204struct Executor {
205    /// Inner state of the executor.
206    inner: Mutex<Inner>,
207
208    /// Used to put idle threads to sleep and wake them up when new work comes in.
209    cvar: Condvar,
210}
211
212/// Inner state of the blocking executor.
213struct Inner {
214    /// Number of idle threads in the pool.
215    ///
216    /// Idle threads are sleeping, waiting to get a task to run.
217    idle_count: usize,
218
219    /// Total number of threads in the pool.
220    ///
221    /// This is the number of idle threads + the number of active threads.
222    thread_count: usize,
223
224    /// The queue of blocking tasks.
225    queue: VecDeque<Runnable>,
226
227    /// Maximum number of threads in the pool
228    thread_limit: Option<NonZeroUsize>,
229}
230
231impl Executor {
232    #[cfg(not(target_family = "wasm"))]
233    fn max_threads() -> NonZeroUsize {
234        match env::var(MAX_THREADS_ENV) {
235            Ok(v) => v
236                .parse::<usize>()
237                .ok()
238                .and_then(|v| NonZeroUsize::new(v.clamp(MIN_MAX_THREADS, MAX_MAX_THREADS)))
239                .unwrap_or(DEFAULT_MAX_THREADS),
240            Err(_) => DEFAULT_MAX_THREADS,
241        }
242    }
243
244    #[cfg(target_family = "wasm")]
245    fn max_threads() -> NonZeroUsize {
246        NonZeroUsize::new(1).unwrap()
247    }
248
249    /// Get a reference to the global executor.
250    #[inline]
251    fn get() -> &'static Self {
252        #[cfg(not(target_family = "wasm"))]
253        {
254            static EXECUTOR: Executor = Executor {
255                inner: Mutex::new(Inner {
256                    idle_count: 0,
257                    thread_count: 0,
258                    queue: VecDeque::new(),
259                    thread_limit: None,
260                }),
261                cvar: Condvar::new(),
262            };
263
264            &EXECUTOR
265        }
266
267        #[cfg(target_family = "wasm")]
268        panic!("cannot spawn a blocking task on WASM")
269    }
270
271    /// Spawns a future onto this executor.
272    ///
273    /// Returns a [`Task`] handle for the spawned task.
274    fn spawn<T: Send + 'static>(future: impl Future<Output = T> + Send + 'static) -> Task<T> {
275        let (runnable, task) = async_task::Builder::new().propagate_panic(true).spawn(
276            move |()| future,
277            |r| {
278                // Initialize the executor if we haven't already.
279                let executor = Self::get();
280
281                // Schedule the task on our executor.
282                executor.schedule(r)
283            },
284        );
285        runnable.schedule();
286        task
287    }
288
289    /// Runs the main loop on the current thread.
290    ///
291    /// This function runs blocking tasks until it becomes idle and times out.
292    fn main_loop(&'static self) {
293        #[cfg(feature = "tracing")]
294        let _span = tracing::trace_span!("blocking::main_loop").entered();
295
296        let mut inner = self.inner.lock().unwrap_or_else(PoisonError::into_inner);
297        loop {
298            // This thread is not idle anymore because it's going to run tasks.
299            inner.idle_count -= 1;
300
301            // Run tasks in the queue.
302            while let Some(runnable) = inner.queue.pop_front() {
303                // We have found a task - grow the pool if needed.
304                self.grow_pool(inner);
305
306                // Run the task.
307                panic::catch_unwind(|| runnable.run()).ok();
308
309                // Re-lock the inner state and continue.
310                inner = self.inner.lock().unwrap_or_else(PoisonError::into_inner);
311            }
312
313            // This thread is now becoming idle.
314            inner.idle_count += 1;
315
316            // Put the thread to sleep until another task is scheduled.
317            let timeout = Duration::from_millis(500);
318            #[cfg(feature = "tracing")]
319            tracing::trace!(?timeout, "going to sleep");
320            let (lock, res) = self.cvar.wait_timeout(inner, timeout).unwrap();
321            inner = lock;
322
323            let over_limit =
324                Some(inner.thread_count) > inner.thread_limit.map(NonZeroUsize::get);
325            if inner.queue.is_empty() && (res.timed_out() || over_limit) {
326                inner.idle_count -= 1;
327                inner.thread_count -= 1;
328                break;
329            }
330
331            #[cfg(feature = "tracing")]
332            tracing::trace!("notified");
333        }
334
335        #[cfg(feature = "tracing")]
336        tracing::trace!("shutting down due to lack of tasks");
337    }
338
339    /// Schedules a runnable task for execution.
340    fn schedule(&'static self, runnable: Runnable) {
341        let mut inner = self.inner.lock().unwrap_or_else(PoisonError::into_inner);
342        inner.queue.push_back(runnable);
343
344        // Notify a sleeping thread and spawn more threads if needed.
345        self.cvar.notify_one();
346        self.grow_pool(inner);
347    }
348
349    /// Spawns more blocking threads if the pool is overloaded with work.
350    fn grow_pool(&'static self, mut inner: MutexGuard<'static, Inner>) {
351        #[cfg(feature = "tracing")]
352        let _span = tracing::trace_span!(
353            "grow_pool",
354            queue_len = inner.queue.len(),
355            idle_count = inner.idle_count,
356            thread_count = inner.thread_count,
357        )
358        .entered();
359
360        loop {
361            let thread_limit = inner
362                .thread_limit
363                .get_or_insert_with(Self::max_threads)
364                .get();
365
366            if inner.queue.len() <= inner.idle_count * 5 || inner.thread_count >= thread_limit {
367                return;
368            }
369
370            #[cfg(feature = "tracing")]
371            tracing::trace!("spawning a new thread to handle blocking tasks");
372
373            inner.idle_count += 1;
374            inner.thread_count += 1;
375            self.cvar.notify_all();
376
377            static ID: AtomicUsize = AtomicUsize::new(1);
378            let id = ID.fetch_add(1, Ordering::Relaxed);
379
380            if consume_spawn_failure() {
381                roll_back_failed_spawn(&mut inner);
382                return;
383            }
384
385            let name = format!("blocking-{id}");
386            drop(inner);
387            let spawned = thread::Builder::new()
388                .name(name)
389                .spawn(move || self.main_loop());
390            inner = self.inner.lock().unwrap_or_else(PoisonError::into_inner);
391
392            if let Err(_e) = spawned {
393                #[cfg(feature = "tracing")]
394                tracing::error!("failed to spawn a blocking thread: {}", _e);
395                roll_back_failed_spawn(&mut inner);
396                return;
397            }
398        }
399    }
400}
401
402fn roll_back_failed_spawn(inner: &mut Inner) {
403    inner.idle_count -= 1;
404    inner.thread_count -= 1;
405    let new_limit = inner.thread_count;
406    inner.thread_limit = Some(NonZeroUsize::new(new_limit).unwrap_or_else(|| {
407        #[cfg(feature = "tracing")]
408        tracing::warn!("attempted to lower thread_limit to zero; setting to one instead");
409        NonZeroUsize::new(1).unwrap()
410    }));
411}
412
413/// Runs blocking code on a thread pool.
414///
415/// # Examples
416///
417/// Read the contents of a file:
418///
419/// ```no_run
420/// use blocking::unblock;
421/// use std::fs;
422///
423/// # futures_lite::future::block_on(async {
424/// let contents = unblock(|| fs::read_to_string("file.txt")).await?;
425/// # std::io::Result::Ok(()) });
426/// ```
427///
428/// Spawn a process:
429///
430/// ```no_run
431/// use blocking::unblock;
432/// use std::process::Command;
433///
434/// # futures_lite::future::block_on(async {
435/// let out = unblock(|| Command::new("dir").output()).await?;
436/// # std::io::Result::Ok(()) });
437/// ```
438pub fn unblock<T, F>(f: F) -> Task<T>
439where
440    F: FnOnce() -> T + Send + 'static,
441    T: Send + 'static,
442{
443    Executor::spawn(async move { f() })
444}
445
446/// Runs blocking I/O on a thread pool.
447///
448/// Blocking I/O must be isolated from async code. This type moves blocking I/O operations onto a
449/// special thread pool while exposing a familiar async interface.
450///
451/// This type implements traits [`Stream`], [`AsyncRead`], [`AsyncWrite`], or [`AsyncSeek`] if the
452/// inner type implements [`Iterator`], [`Read`], [`Write`], or [`Seek`], respectively.
453///
454/// # Caveats
455///
456/// [`Unblock`] is a low-level primitive, and as such it comes with some caveats.
457///
458/// For higher-level primitives built on top of [`Unblock`], look into [`async-fs`].
459///
460/// [`async-fs`]: https://github.com/smol-rs/async-fs
461///
462/// [`Unblock`] communicates with I/O operations on the thread pool through a pipe. That means an
463/// async read/write operation simply receives/sends some bytes from/into the pipe. When in reading
464/// mode, the thread pool reads bytes from the I/O handle and forwards them into the pipe until it
465/// becomes full. When in writing mode, the thread pool reads bytes from the pipe and forwards them
466/// into the I/O handle.
467///
468/// Use [`Unblock::with_capacity()`] to configure the capacity of the pipe.
469///
470/// ### Reading
471///
472/// If you create an [`Unblock`]`<`[`Stdin`][`std::io::Stdin`]`>`, read some bytes from it,
473/// and then drop it, a blocked read operation may keep hanging on the thread pool. The next
474/// attempt to read from stdin will lose bytes read by the hanging operation. This is a difficult
475/// problem to solve, so make sure you only use a single stdin handle for the duration of the
476/// entire program.
477///
478/// ### Writing
479///
480/// If writing data through the [`AsyncWrite`] trait, make sure to flush before dropping the
481/// [`Unblock`] handle or some buffered data might get lost.
482///
483/// ### Seeking
484///
485/// Because of buffering in the pipe, if [`Unblock`] wraps a [`File`][`std::fs::File`], a single
486/// read operation may move the file cursor farther than is the span of the operation. In fact,
487/// reading just keeps going in the background until the pipe gets full. Keep this mind when
488/// using [`AsyncSeek`] with [relative][`SeekFrom::Current`] offsets.
489///
490/// # Examples
491///
492/// ```
493/// use blocking::Unblock;
494/// use futures_lite::prelude::*;
495///
496/// # futures_lite::future::block_on(async {
497/// let mut stdout = Unblock::new(std::io::stdout());
498/// stdout.write_all(b"Hello world!").await?;
499/// stdout.flush().await?;
500/// # std::io::Result::Ok(()) });
501/// ```
502pub struct Unblock<T> {
503    state: State<T>,
504    cap: Option<usize>,
505    write_error: Option<io::Error>,
506    needs_flush: bool,
507}
508
509impl<T> Unblock<T> {
510    /// Wraps a blocking I/O handle into the async [`Unblock`] interface.
511    ///
512    /// # Examples
513    ///
514    /// ```no_run
515    /// use blocking::Unblock;
516    ///
517    /// let stdin = Unblock::new(std::io::stdin());
518    /// ```
519    pub fn new(io: T) -> Unblock<T> {
520        Unblock {
521            state: State::Idle(Some(Box::new(io))),
522            cap: None,
523            write_error: None,
524            needs_flush: true,
525        }
526    }
527
528    /// Wraps a blocking I/O handle into the async [`Unblock`] interface with a custom buffer
529    /// capacity.
530    ///
531    /// When communicating with the inner [`Stream`]/[`Read`]/[`Write`] type from async code, data
532    /// transferred between blocking and async code goes through a buffer of limited capacity. This
533    /// constructor configures that capacity.
534    ///
535    /// The default capacity is:
536    ///
537    /// * For [`Iterator`] types: 8192 items.
538    /// * For [`Read`]/[`Write`] types: 8 MB.
539    ///
540    /// # Examples
541    ///
542    /// ```no_run
543    /// use blocking::Unblock;
544    ///
545    /// let stdout = Unblock::with_capacity(64 * 1024, std::io::stdout());
546    /// ```
547    pub fn with_capacity(cap: usize, io: T) -> Unblock<T> {
548        Unblock {
549            state: State::Idle(Some(Box::new(io))),
550            cap: Some(cap),
551            write_error: None,
552            needs_flush: true,
553        }
554    }
555
556    /// Gets a mutable reference to the blocking I/O handle.
557    ///
558    /// This is an async method because the I/O handle might be on the thread pool and needs to
559    /// be moved onto the current thread before we can get a reference to it.
560    ///
561    /// # Examples
562    ///
563    /// ```no_run
564    /// use blocking::{unblock, Unblock};
565    /// use std::fs::File;
566    ///
567    /// # futures_lite::future::block_on(async {
568    /// let file = unblock(|| File::create("file.txt")).await?;
569    /// let mut file = Unblock::new(file);
570    ///
571    /// let metadata = file.get_mut().await.metadata()?;
572    /// # std::io::Result::Ok(()) });
573    /// ```
574    pub async fn get_mut(&mut self) -> &mut T {
575        // Wait for the running task to stop and ignore I/O errors if there are any.
576        future::poll_fn(|cx| self.poll_stop(cx)).await.ok();
577        self.needs_flush = true;
578
579        // Assume idle state and get a reference to the inner value.
580        match &mut self.state {
581            State::Idle(t) => t.as_mut().expect("inner value was taken out"),
582            State::WithMut(..)
583            | State::Streaming(..)
584            | State::Reading(..)
585            | State::Writing(..)
586            | State::Seeking(..)
587            | State::Flushing(..) => {
588                unreachable!("when stopped, the state machine must be in idle state");
589            }
590        }
591    }
592
593    /// Performs a blocking operation on the I/O handle.
594    ///
595    /// # Examples
596    ///
597    /// ```no_run
598    /// use blocking::{unblock, Unblock};
599    /// use std::fs::File;
600    ///
601    /// # futures_lite::future::block_on(async {
602    /// let file = unblock(|| File::create("file.txt")).await?;
603    /// let mut file = Unblock::new(file);
604    ///
605    /// let metadata = file.with_mut(|f| f.metadata()).await?;
606    /// # std::io::Result::Ok(()) });
607    /// ```
608    pub async fn with_mut<R, F>(&mut self, op: F) -> R
609    where
610        F: FnOnce(&mut T) -> R + Send + 'static,
611        R: Send + 'static,
612        T: Send + 'static,
613    {
614        // Wait for the running task to stop and ignore I/O errors if there are any.
615        future::poll_fn(|cx| self.poll_stop(cx)).await.ok();
616
617        // Assume idle state and take out the inner value.
618        let mut t = match &mut self.state {
619            State::Idle(t) => t.take().expect("inner value was taken out"),
620            State::WithMut(..)
621            | State::Streaming(..)
622            | State::Reading(..)
623            | State::Writing(..)
624            | State::Seeking(..)
625            | State::Flushing(..) => {
626                unreachable!("when stopped, the state machine must be in idle state");
627            }
628        };
629
630        let (sender, receiver) = bounded(1);
631        let task = Executor::spawn(async move {
632            sender.try_send(op(&mut t)).ok();
633            t
634        });
635        self.state = State::WithMut(task);
636
637        receiver
638            .recv()
639            .await
640            .expect("`Unblock::with_mut()` operation has panicked")
641    }
642
643    /// Extracts the inner blocking I/O handle.
644    ///
645    /// This is an async method because the I/O handle might be on the thread pool and needs to
646    /// be moved onto the current thread before we can extract it.
647    ///
648    /// # Examples
649    ///
650    /// ```no_run
651    /// use blocking::{unblock, Unblock};
652    /// use futures_lite::prelude::*;
653    /// use std::fs::File;
654    ///
655    /// # futures_lite::future::block_on(async {
656    /// let file = unblock(|| File::create("file.txt")).await?;
657    /// let file = Unblock::new(file);
658    ///
659    /// let file = file.into_inner().await;
660    /// # std::io::Result::Ok(()) });
661    /// ```
662    pub async fn into_inner(self) -> T {
663        // There's a bug in rustdoc causing it to render `mut self` as `__arg0: Self`, so we just
664        // bind `self` to a local mutable variable.
665        let mut this = self;
666
667        // Wait for the running task to stop and ignore I/O errors if there are any.
668        future::poll_fn(|cx| this.poll_stop(cx)).await.ok();
669
670        // Assume idle state and extract the inner value.
671        match &mut this.state {
672            State::Idle(t) => *t.take().expect("inner value was taken out"),
673            State::WithMut(..)
674            | State::Streaming(..)
675            | State::Reading(..)
676            | State::Writing(..)
677            | State::Seeking(..)
678            | State::Flushing(..) => {
679                unreachable!("when stopped, the state machine must be in idle state");
680            }
681        }
682    }
683
684    /// Waits for the running task to stop.
685    ///
686    /// On success, the state machine is moved into the idle state.
687    fn poll_stop(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
688        loop {
689            match &mut self.state {
690                State::Idle(_) => return Poll::Ready(Ok(())),
691
692                State::WithMut(task) => {
693                    // Poll the task to wait for it to finish.
694                    let io = ready!(Pin::new(task).poll(cx));
695                    self.state = State::Idle(Some(io));
696                    self.needs_flush = true;
697                }
698
699                State::Streaming(any, task) => {
700                    // Drop the receiver to close the channel. This stops the `send()` operation in
701                    // the task, after which the task returns the iterator back.
702                    any.take();
703
704                    // Poll the task to retrieve the iterator.
705                    let iter = ready!(Pin::new(task).poll(cx));
706                    self.state = State::Idle(Some(iter));
707                }
708
709                State::Reading(reader, task) => {
710                    // Drop the reader to close the pipe. This stops copying inside the task, after
711                    // which the task returns the I/O handle back.
712                    reader.take();
713
714                    // Poll the task to retrieve the I/O handle.
715                    let (res, io) = ready!(Pin::new(task).poll(cx));
716                    // Make sure to move into the idle state before reporting errors.
717                    self.state = State::Idle(Some(io));
718                    res?;
719                }
720
721                State::Writing(writer, task) => {
722                    // Drop the writer to close the pipe. This stops copying inside the task, after
723                    // which the task flushes the I/O handle and
724                    writer.take();
725
726                    // Poll the task to retrieve the I/O handle.
727                    let (res, io) = ready!(Pin::new(task).poll(cx));
728                    // Make sure to move into the idle state before reporting errors.
729                    self.state = State::Idle(Some(io));
730                    if res.is_ok() {
731                        self.needs_flush = false;
732                    }
733                    res?;
734                }
735
736                State::Flushing(task) => {
737                    let (res, io) = ready!(Pin::new(task).poll(cx));
738                    self.state = State::Idle(Some(io));
739                    if res.is_ok() {
740                        self.needs_flush = false;
741                    }
742                    res?;
743                }
744
745                State::Seeking(task) => {
746                    // Poll the task to wait for it to finish.
747                    let (_, res, io) = ready!(Pin::new(task).poll(cx));
748                    // Make sure to move into the idle state before reporting errors.
749                    self.state = State::Idle(Some(io));
750                    res?;
751                }
752            }
753        }
754    }
755}
756
757impl<T: fmt::Debug> fmt::Debug for Unblock<T> {
758    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
759        struct Closed;
760        impl fmt::Debug for Closed {
761            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
762                f.write_str("<closed>")
763            }
764        }
765
766        struct Blocked;
767        impl fmt::Debug for Blocked {
768            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
769                f.write_str("<blocked>")
770            }
771        }
772
773        match &self.state {
774            State::Idle(None) => f.debug_struct("Unblock").field("io", &Closed).finish(),
775            State::Idle(Some(io)) => {
776                let io: &T = io;
777                f.debug_struct("Unblock").field("io", io).finish()
778            }
779            State::WithMut(..)
780            | State::Streaming(..)
781            | State::Reading(..)
782            | State::Writing(..)
783            | State::Seeking(..)
784            | State::Flushing(..) => f.debug_struct("Unblock").field("io", &Blocked).finish(),
785        }
786    }
787}
788
789/// Current state of a blocking task.
790enum State<T> {
791    /// There is no blocking task.
792    ///
793    /// The inner value is readily available, unless it has already been extracted. The value is
794    /// extracted out by [`Unblock::into_inner()`], [`AsyncWrite::poll_close()`], or by awaiting
795    /// [`Unblock`].
796    Idle(Option<Box<T>>),
797
798    /// A [`Unblock::with_mut()`] closure was spawned and is still running.
799    WithMut(Task<Box<T>>),
800
801    /// The inner value is an [`Iterator`] currently iterating in a task.
802    ///
803    /// The `dyn Any` value here is a `Pin<Box<Receiver<<T as Iterator>::Item>>>`.
804    Streaming(Option<Box<dyn Any + Send + Sync>>, Task<Box<T>>),
805
806    /// The inner value is a [`Read`] currently reading in a task.
807    Reading(Option<Reader>, Task<(io::Result<()>, Box<T>)>),
808
809    /// The inner value is a [`Write`] currently writing in a task.
810    Writing(Option<Writer>, Task<(io::Result<()>, Box<T>)>),
811
812    /// The inner value is a [`Seek`] currently seeking in a task.
813    Seeking(Task<(SeekFrom, io::Result<u64>, Box<T>)>),
814
815    /// `T::flush` is running on the pool.
816    Flushing(Task<(io::Result<()>, Box<T>)>),
817}
818
819impl<T: Iterator + Send + 'static> Stream for Unblock<T>
820where
821    T::Item: Send + 'static,
822{
823    type Item = T::Item;
824
825    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T::Item>> {
826        loop {
827            match &mut self.state {
828                // If not in idle or active streaming state, stop the running task.
829                State::WithMut(..)
830                | State::Streaming(None, _)
831                | State::Reading(..)
832                | State::Writing(..)
833                | State::Seeking(..)
834                | State::Flushing(..) => {
835                    // Wait for the running task to stop.
836                    ready!(self.poll_stop(cx)).ok();
837                }
838
839                // If idle, start a streaming task.
840                State::Idle(iter) => {
841                    // Take the iterator out to run it on a blocking task.
842                    let mut iter = iter.take().expect("inner iterator was taken out");
843
844                    // This channel capacity seems to work well in practice. If it's too low, there
845                    // will be too much synchronization between tasks. If too high, memory
846                    // consumption increases.
847                    let (sender, receiver) = bounded(self.cap.unwrap_or(8 * 1024)); // 8192 items
848
849                    // Spawn a blocking task that runs the iterator and returns it when done.
850                    let task = Executor::spawn(async move {
851                        for item in &mut iter {
852                            if sender.send(item).await.is_err() {
853                                break;
854                            }
855                        }
856                        iter
857                    });
858
859                    // Move into the busy state and poll again.
860                    self.state = State::Streaming(Some(Box::new(Box::pin(receiver))), task);
861                }
862
863                // If streaming, receive an item.
864                State::Streaming(Some(any), task) => {
865                    let receiver = any.downcast_mut::<Pin<Box<Receiver<T::Item>>>>().unwrap();
866
867                    // Poll the channel.
868                    let opt = ready!(receiver.as_mut().poll_next(cx));
869
870                    // If the channel is closed, retrieve the iterator back from the blocking task.
871                    // This is not really a required step, but it's cleaner to drop the iterator on
872                    // the same thread that created it.
873                    if opt.is_none() {
874                        // Poll the task to retrieve the iterator.
875                        let iter = ready!(Pin::new(task).poll(cx));
876                        self.state = State::Idle(Some(iter));
877                    }
878
879                    return Poll::Ready(opt);
880                }
881            }
882        }
883    }
884}
885
886fn retry_interrupted<T>(mut operation: impl FnMut() -> io::Result<T>) -> io::Result<T> {
887    loop {
888        match operation() {
889            Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
890            result => return result,
891        }
892    }
893}
894
895struct Transfer<'a, T>(&'a mut T);
896
897impl<T: Read> Read for Transfer<'_, T> {
898    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
899        retry_interrupted(|| self.0.read(buf))
900    }
901}
902
903impl<T: Write> Write for Transfer<'_, T> {
904    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
905        match retry_interrupted(|| self.0.write(buf))? {
906            0 if !buf.is_empty() => Err(io::ErrorKind::WriteZero.into()),
907            count => Ok(count),
908        }
909    }
910
911    fn flush(&mut self) -> io::Result<()> {
912        retry_interrupted(|| self.0.flush())
913    }
914}
915
916impl<T: Read + Send + 'static> AsyncRead for Unblock<T> {
917    fn poll_read(
918        mut self: Pin<&mut Self>,
919        cx: &mut Context<'_>,
920        buf: &mut [u8],
921    ) -> Poll<io::Result<usize>> {
922        if buf.is_empty() {
923            return Poll::Ready(Ok(0));
924        }
925
926        loop {
927            match &mut self.state {
928                // If not in idle or active reading state, stop the running task.
929                State::WithMut(..)
930                | State::Reading(None, _)
931                | State::Streaming(..)
932                | State::Writing(..)
933                | State::Seeking(..)
934                | State::Flushing(..) => {
935                    // Wait for the running task to stop.
936                    ready!(self.poll_stop(cx))?;
937                }
938
939                // If idle, start a reading task.
940                State::Idle(io) => {
941                    // Take the I/O handle out to read it on a blocking task.
942                    let mut io = io.take().expect("inner value was taken out");
943
944                    // This pipe capacity seems to work well in practice. If it's too low, there
945                    // will be too much synchronization between tasks. If too high, memory
946                    // consumption increases.
947                    let (reader, mut writer) = pipe(self.cap.unwrap_or(8 * 1024 * 1024)); // 8 MB
948
949                    // Spawn a blocking task that reads and returns the I/O handle when done.
950                    let task = Executor::spawn(async move {
951                        // Copy bytes from the I/O handle into the pipe until the pipe is closed or
952                        // an error occurs.
953                        loop {
954                            match future::poll_fn(|cx| writer.poll_fill(cx, Transfer(&mut io))).await {
955                                Ok(0) => return (Ok(()), io),
956                                Ok(_) => {}
957                                Err(err) => return (Err(err), io),
958                            }
959                        }
960                    });
961
962                    // Move into the busy state and poll again.
963                    self.state = State::Reading(Some(reader), task);
964                }
965
966                // If reading, read bytes from the pipe.
967                State::Reading(Some(reader), task) => {
968                    // Poll the pipe.
969                    let n = ready!(reader.poll_drain(cx, buf))?;
970
971                    // If the pipe is closed, retrieve the I/O handle back from the blocking task.
972                    // This is not really a required step, but it's cleaner to drop the handle on
973                    // the same thread that created it.
974                    if n == 0 {
975                        // Poll the task to retrieve the I/O handle.
976                        let (res, io) = ready!(Pin::new(task).poll(cx));
977                        // Make sure to move into the idle state before reporting errors.
978                        self.state = State::Idle(Some(io));
979                        res?;
980                    }
981
982                    return Poll::Ready(Ok(n));
983                }
984            }
985        }
986    }
987}
988
989impl<T: Write + Send + 'static> AsyncWrite for Unblock<T> {
990    fn poll_write(
991        mut self: Pin<&mut Self>,
992        cx: &mut Context<'_>,
993        buf: &[u8],
994    ) -> Poll<io::Result<usize>> {
995        if buf.is_empty() {
996            if let Some(err) = self.write_error.take() {
997                return Poll::Ready(Err(err));
998            }
999            return Poll::Ready(Ok(0));
1000        }
1001
1002        loop {
1003            if let Some(err) = self.write_error.take() {
1004                return Poll::Ready(Err(err));
1005            }
1006
1007            let finished = match &mut self.state {
1008                // If not in idle or active writing state, stop the running task.
1009                State::WithMut(..)
1010                | State::Writing(None, _)
1011                | State::Streaming(..)
1012                | State::Reading(..)
1013                | State::Seeking(..)
1014                | State::Flushing(..) => {
1015                    // Wait for the running task to stop.
1016                    ready!(self.poll_stop(cx))?;
1017                    None
1018                }
1019
1020                // If idle, start the writing task.
1021                State::Idle(io) => {
1022                    // Take the I/O handle out to write on a blocking task.
1023                    let mut io = io.take().expect("inner value was taken out");
1024
1025                    // This pipe capacity seems to work well in practice. If it's too low, there will
1026                    // be too much synchronization between tasks. If too high, memory consumption
1027                    // increases.
1028                    let (mut reader, writer) = pipe(self.cap.unwrap_or(8 * 1024 * 1024)); // 8 MB
1029
1030                    // Spawn a blocking task that writes and returns the I/O handle when done.
1031                    let task = Executor::spawn(async move {
1032                        // Copy bytes from the pipe into the I/O handle until the pipe is closed or an
1033                        // error occurs. Flush the I/O handle at the end.
1034                        loop {
1035                            match future::poll_fn(|cx| reader.poll_drain(cx, Transfer(&mut io))).await {
1036                                Ok(0) => return (retry_interrupted(|| io.flush()), io),
1037                                Ok(_) => {}
1038                                Err(err) => {
1039                                    retry_interrupted(|| io.flush()).ok();
1040                                    return (Err(err), io);
1041                                }
1042                            }
1043                        }
1044                    });
1045
1046                    self.needs_flush = true;
1047                    self.state = State::Writing(Some(writer), task);
1048                    None
1049                }
1050
1051                // If writing, write more bytes into the pipe.
1052                State::Writing(Some(writer), task) => {
1053                    if !writer.is_closed() {
1054                        match writer.poll_fill(cx, buf) {
1055                            Poll::Pending => return Poll::Pending,
1056                            Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1057                            Poll::Ready(Ok(n)) if n > 0 => return Poll::Ready(Ok(n)),
1058                            Poll::Ready(Ok(_)) => {}
1059                        }
1060                    }
1061
1062                    match Pin::new(task).poll(cx) {
1063                        Poll::Pending => return Poll::Pending,
1064                        Poll::Ready((res, io)) => Some((res, io)),
1065                    }
1066                }
1067            };
1068
1069            if let Some((res, io)) = finished {
1070                self.state = State::Idle(Some(io));
1071                match res {
1072                    Ok(()) => {
1073                        self.needs_flush = false;
1074                        return Poll::Ready(Ok(0));
1075                    }
1076                    Err(err) => self.write_error = Some(err),
1077                }
1078            }
1079        }
1080    }
1081
1082    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1083        loop {
1084            if let Some(err) = self.write_error.take() {
1085                return Poll::Ready(Err(err));
1086            }
1087            if matches!(self.state, State::Idle(None))
1088                || (matches!(self.state, State::Idle(Some(_))) && !self.needs_flush)
1089            {
1090                return Poll::Ready(Ok(()));
1091            }
1092
1093            match &mut self.state {
1094                State::Flushing(task) => {
1095                    let (res, io) = ready!(Pin::new(task).poll(cx));
1096                    self.state = State::Idle(Some(io));
1097                    return match res {
1098                        Ok(()) => {
1099                            self.needs_flush = false;
1100                            Poll::Ready(Ok(()))
1101                        }
1102                        Err(err) => Poll::Ready(Err(err)),
1103                    };
1104                }
1105
1106                // If not in idle state, stop the running task.
1107                State::WithMut(..)
1108                | State::Streaming(..)
1109                | State::Writing(..)
1110                | State::Reading(..)
1111                | State::Seeking(..) => {
1112                    // Wait for the running task to stop.
1113                    ready!(self.poll_stop(cx))?;
1114                }
1115
1116                State::Idle(io) => {
1117                    let mut io = io.take().expect("inner value was taken out");
1118                    let task = Executor::spawn(async move {
1119                        let res = retry_interrupted(|| io.flush());
1120                        (res, io)
1121                    });
1122                    self.state = State::Flushing(task);
1123                }
1124            }
1125        }
1126    }
1127
1128    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1129        // First, make sure the I/O handle is flushed.
1130        ready!(Pin::new(&mut self).poll_flush(cx))?;
1131
1132        // Then move into the idle state with no I/O handle, thus dropping it.
1133        self.state = State::Idle(None);
1134        Poll::Ready(Ok(()))
1135    }
1136}
1137
1138impl<T: Seek + Send + 'static> AsyncSeek for Unblock<T> {
1139    fn poll_seek(
1140        mut self: Pin<&mut Self>,
1141        cx: &mut Context<'_>,
1142        pos: SeekFrom,
1143    ) -> Poll<io::Result<u64>> {
1144        loop {
1145            match &mut self.state {
1146                // If not in idle state, stop the running task.
1147                State::WithMut(..)
1148                | State::Streaming(..)
1149                | State::Reading(..)
1150                | State::Writing(..)
1151                | State::Flushing(..) => {
1152                    // Wait for the running task to stop.
1153                    ready!(self.poll_stop(cx))?;
1154                }
1155
1156                State::Idle(io) => {
1157                    // Take the I/O handle out to seek on a blocking task.
1158                    let mut io = io.take().expect("inner value was taken out");
1159
1160                    let task = Executor::spawn(async move {
1161                        let res = retry_interrupted(|| io.seek(pos));
1162                        (pos, res, io)
1163                    });
1164                    self.state = State::Seeking(task);
1165                }
1166
1167                State::Seeking(task) => {
1168                    // Poll the task to wait for it to finish.
1169                    let (original_pos, res, io) = ready!(Pin::new(task).poll(cx));
1170                    // Make sure to move into the idle state before reporting errors.
1171                    self.state = State::Idle(Some(io));
1172                    let current = res?;
1173
1174                    // If the `pos` argument matches the original one, return the result.
1175                    if original_pos == pos {
1176                        return Poll::Ready(Ok(current));
1177                    }
1178                }
1179            }
1180        }
1181    }
1182}
1183
1184#[cfg(all(test, not(target_family = "wasm")))]
1185mod tests {
1186    use super::*;
1187
1188    use std::sync::mpsc;
1189    use std::time::Duration;
1190
1191    struct ResetSpawnFailures;
1192
1193    impl Drop for ResetSpawnFailures {
1194        fn drop(&mut self) {
1195            SPAWN_FAILURES.store(0, Ordering::SeqCst);
1196        }
1197    }
1198
1199    #[test]
1200    fn spawn_failure_returns() {
1201        let _reset = ResetSpawnFailures;
1202        SPAWN_FAILURES.store(1_000, Ordering::SeqCst);
1203        let (tx, rx) = mpsc::channel();
1204        thread::spawn(move || {
1205            let _task = unblock(|| ());
1206            tx.send(()).unwrap();
1207        });
1208        rx.recv_timeout(Duration::from_secs(1))
1209            .expect("schedule returned");
1210    }
1211
1212    #[test]
1213    fn test_max_threads() {
1214        // properly set env var
1215        env::set_var(MAX_THREADS_ENV, "100");
1216        assert_eq!(100, Executor::max_threads().get());
1217
1218        // passed value below minimum, so we set it to minimum
1219        env::set_var(MAX_THREADS_ENV, "0");
1220        assert_eq!(1, Executor::max_threads().get());
1221
1222        // passed value above maximum, so we set to allowed maximum
1223        env::set_var(MAX_THREADS_ENV, "50000");
1224        assert_eq!(10000, Executor::max_threads().get());
1225
1226        // no env var, use default
1227        env::set_var(MAX_THREADS_ENV, "");
1228        assert_eq!(500, Executor::max_threads().get());
1229
1230        // not a number, use default
1231        env::set_var(MAX_THREADS_ENV, "NOTINT");
1232        assert_eq!(500, Executor::max_threads().get());
1233    }
1234}