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
886impl<T: Read + Send + 'static> AsyncRead for Unblock<T> {
887    fn poll_read(
888        mut self: Pin<&mut Self>,
889        cx: &mut Context<'_>,
890        buf: &mut [u8],
891    ) -> Poll<io::Result<usize>> {
892        if buf.is_empty() {
893            return Poll::Ready(Ok(0));
894        }
895
896        loop {
897            match &mut self.state {
898                // If not in idle or active reading state, stop the running task.
899                State::WithMut(..)
900                | State::Reading(None, _)
901                | State::Streaming(..)
902                | State::Writing(..)
903                | State::Seeking(..)
904                | State::Flushing(..) => {
905                    // Wait for the running task to stop.
906                    ready!(self.poll_stop(cx))?;
907                }
908
909                // If idle, start a reading task.
910                State::Idle(io) => {
911                    // Take the I/O handle out to read it on a blocking task.
912                    let mut io = io.take().expect("inner value was taken out");
913
914                    // This pipe capacity seems to work well in practice. If it's too low, there
915                    // will be too much synchronization between tasks. If too high, memory
916                    // consumption increases.
917                    let (reader, mut writer) = pipe(self.cap.unwrap_or(8 * 1024 * 1024)); // 8 MB
918
919                    // Spawn a blocking task that reads and returns the I/O handle when done.
920                    let task = Executor::spawn(async move {
921                        // Copy bytes from the I/O handle into the pipe until the pipe is closed or
922                        // an error occurs.
923                        loop {
924                            match future::poll_fn(|cx| writer.poll_fill(cx, &mut io)).await {
925                                Ok(0) => return (Ok(()), io),
926                                Ok(_) => {}
927                                Err(err) => return (Err(err), io),
928                            }
929                        }
930                    });
931
932                    // Move into the busy state and poll again.
933                    self.state = State::Reading(Some(reader), task);
934                }
935
936                // If reading, read bytes from the pipe.
937                State::Reading(Some(reader), task) => {
938                    // Poll the pipe.
939                    let n = ready!(reader.poll_drain(cx, buf))?;
940
941                    // If the pipe is closed, retrieve the I/O handle back from the blocking task.
942                    // This is not really a required step, but it's cleaner to drop the handle on
943                    // the same thread that created it.
944                    if n == 0 {
945                        // Poll the task to retrieve the I/O handle.
946                        let (res, io) = ready!(Pin::new(task).poll(cx));
947                        // Make sure to move into the idle state before reporting errors.
948                        self.state = State::Idle(Some(io));
949                        res?;
950                    }
951
952                    return Poll::Ready(Ok(n));
953                }
954            }
955        }
956    }
957}
958
959impl<T: Write + Send + 'static> AsyncWrite for Unblock<T> {
960    fn poll_write(
961        mut self: Pin<&mut Self>,
962        cx: &mut Context<'_>,
963        buf: &[u8],
964    ) -> Poll<io::Result<usize>> {
965        if buf.is_empty() {
966            if let Some(err) = self.write_error.take() {
967                return Poll::Ready(Err(err));
968            }
969            return Poll::Ready(Ok(0));
970        }
971
972        loop {
973            if let Some(err) = self.write_error.take() {
974                return Poll::Ready(Err(err));
975            }
976
977            let finished = match &mut self.state {
978                // If not in idle or active writing state, stop the running task.
979                State::WithMut(..)
980                | State::Writing(None, _)
981                | State::Streaming(..)
982                | State::Reading(..)
983                | State::Seeking(..)
984                | State::Flushing(..) => {
985                    // Wait for the running task to stop.
986                    ready!(self.poll_stop(cx))?;
987                    None
988                }
989
990                // If idle, start the writing task.
991                State::Idle(io) => {
992                    // Take the I/O handle out to write on a blocking task.
993                    let mut io = io.take().expect("inner value was taken out");
994
995                    // This pipe capacity seems to work well in practice. If it's too low, there will
996                    // be too much synchronization between tasks. If too high, memory consumption
997                    // increases.
998                    let (mut reader, writer) = pipe(self.cap.unwrap_or(8 * 1024 * 1024)); // 8 MB
999
1000                    // Spawn a blocking task that writes and returns the I/O handle when done.
1001                    let task = Executor::spawn(async move {
1002                        // Copy bytes from the pipe into the I/O handle until the pipe is closed or an
1003                        // error occurs. Flush the I/O handle at the end.
1004                        loop {
1005                            match future::poll_fn(|cx| reader.poll_drain(cx, &mut io)).await {
1006                                Ok(0) => return (io.flush(), io),
1007                                Ok(_) => {}
1008                                Err(err) => {
1009                                    io.flush().ok();
1010                                    return (Err(err), io);
1011                                }
1012                            }
1013                        }
1014                    });
1015
1016                    self.needs_flush = true;
1017                    self.state = State::Writing(Some(writer), task);
1018                    None
1019                }
1020
1021                // If writing, write more bytes into the pipe.
1022                State::Writing(Some(writer), task) => {
1023                    if !writer.is_closed() {
1024                        match writer.poll_fill(cx, buf) {
1025                            Poll::Pending => return Poll::Pending,
1026                            Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
1027                            Poll::Ready(Ok(n)) if n > 0 => return Poll::Ready(Ok(n)),
1028                            Poll::Ready(Ok(_)) => {}
1029                        }
1030                    }
1031
1032                    match Pin::new(task).poll(cx) {
1033                        Poll::Pending => return Poll::Pending,
1034                        Poll::Ready((res, io)) => Some((res, io)),
1035                    }
1036                }
1037            };
1038
1039            if let Some((res, io)) = finished {
1040                self.state = State::Idle(Some(io));
1041                match res {
1042                    Ok(()) => {
1043                        self.needs_flush = false;
1044                        return Poll::Ready(Ok(0));
1045                    }
1046                    Err(err) => self.write_error = Some(err),
1047                }
1048            }
1049        }
1050    }
1051
1052    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1053        loop {
1054            if let Some(err) = self.write_error.take() {
1055                return Poll::Ready(Err(err));
1056            }
1057            if matches!(self.state, State::Idle(None))
1058                || (matches!(self.state, State::Idle(Some(_))) && !self.needs_flush)
1059            {
1060                return Poll::Ready(Ok(()));
1061            }
1062
1063            match &mut self.state {
1064                State::Flushing(task) => {
1065                    let (res, io) = ready!(Pin::new(task).poll(cx));
1066                    self.state = State::Idle(Some(io));
1067                    return match res {
1068                        Ok(()) => {
1069                            self.needs_flush = false;
1070                            Poll::Ready(Ok(()))
1071                        }
1072                        Err(err) => Poll::Ready(Err(err)),
1073                    };
1074                }
1075
1076                // If not in idle state, stop the running task.
1077                State::WithMut(..)
1078                | State::Streaming(..)
1079                | State::Writing(..)
1080                | State::Reading(..)
1081                | State::Seeking(..) => {
1082                    // Wait for the running task to stop.
1083                    ready!(self.poll_stop(cx))?;
1084                }
1085
1086                State::Idle(io) => {
1087                    let mut io = io.take().expect("inner value was taken out");
1088                    let task = Executor::spawn(async move {
1089                        let res = io.flush();
1090                        (res, io)
1091                    });
1092                    self.state = State::Flushing(task);
1093                }
1094            }
1095        }
1096    }
1097
1098    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1099        // First, make sure the I/O handle is flushed.
1100        ready!(Pin::new(&mut self).poll_flush(cx))?;
1101
1102        // Then move into the idle state with no I/O handle, thus dropping it.
1103        self.state = State::Idle(None);
1104        Poll::Ready(Ok(()))
1105    }
1106}
1107
1108impl<T: Seek + Send + 'static> AsyncSeek for Unblock<T> {
1109    fn poll_seek(
1110        mut self: Pin<&mut Self>,
1111        cx: &mut Context<'_>,
1112        pos: SeekFrom,
1113    ) -> Poll<io::Result<u64>> {
1114        loop {
1115            match &mut self.state {
1116                // If not in idle state, stop the running task.
1117                State::WithMut(..)
1118                | State::Streaming(..)
1119                | State::Reading(..)
1120                | State::Writing(..)
1121                | State::Flushing(..) => {
1122                    // Wait for the running task to stop.
1123                    ready!(self.poll_stop(cx))?;
1124                }
1125
1126                State::Idle(io) => {
1127                    // Take the I/O handle out to seek on a blocking task.
1128                    let mut io = io.take().expect("inner value was taken out");
1129
1130                    let task = Executor::spawn(async move {
1131                        let res = io.seek(pos);
1132                        (pos, res, io)
1133                    });
1134                    self.state = State::Seeking(task);
1135                }
1136
1137                State::Seeking(task) => {
1138                    // Poll the task to wait for it to finish.
1139                    let (original_pos, res, io) = ready!(Pin::new(task).poll(cx));
1140                    // Make sure to move into the idle state before reporting errors.
1141                    self.state = State::Idle(Some(io));
1142                    let current = res?;
1143
1144                    // If the `pos` argument matches the original one, return the result.
1145                    if original_pos == pos {
1146                        return Poll::Ready(Ok(current));
1147                    }
1148                }
1149            }
1150        }
1151    }
1152}
1153
1154#[cfg(all(test, not(target_family = "wasm")))]
1155mod tests {
1156    use super::*;
1157
1158    use std::sync::mpsc;
1159    use std::time::Duration;
1160
1161    struct ResetSpawnFailures;
1162
1163    impl Drop for ResetSpawnFailures {
1164        fn drop(&mut self) {
1165            SPAWN_FAILURES.store(0, Ordering::SeqCst);
1166        }
1167    }
1168
1169    #[test]
1170    fn spawn_failure_returns() {
1171        let _reset = ResetSpawnFailures;
1172        SPAWN_FAILURES.store(1_000, Ordering::SeqCst);
1173        let (tx, rx) = mpsc::channel();
1174        thread::spawn(move || {
1175            let _task = unblock(|| ());
1176            tx.send(()).unwrap();
1177        });
1178        rx.recv_timeout(Duration::from_secs(1))
1179            .expect("schedule returned");
1180    }
1181
1182    #[test]
1183    fn test_max_threads() {
1184        // properly set env var
1185        env::set_var(MAX_THREADS_ENV, "100");
1186        assert_eq!(100, Executor::max_threads().get());
1187
1188        // passed value below minimum, so we set it to minimum
1189        env::set_var(MAX_THREADS_ENV, "0");
1190        assert_eq!(1, Executor::max_threads().get());
1191
1192        // passed value above maximum, so we set to allowed maximum
1193        env::set_var(MAX_THREADS_ENV, "50000");
1194        assert_eq!(10000, Executor::max_threads().get());
1195
1196        // no env var, use default
1197        env::set_var(MAX_THREADS_ENV, "");
1198        assert_eq!(500, Executor::max_threads().get());
1199
1200        // not a number, use default
1201        env::set_var(MAX_THREADS_ENV, "NOTINT");
1202        assert_eq!(500, Executor::max_threads().get());
1203    }
1204}