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}