1212//! job has to finish before others get a chance to run. When a thread is idle, it waits for the
1313//! next job or shuts down after a certain timeout.
1414//!
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.
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.
1718//!
1819//! [IOCP]: https://en.wikipedia.org/wiki/Input/output_completion_port
1920//! [AIO]: http://man7.org/linux/man-pages/man2/io_submit.2.html
@@ -92,7 +93,7 @@ use std::num::NonZeroUsize;
9293use std:: panic;
9394use std:: pin:: Pin ;
9495use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
95- use std:: sync:: { Condvar , Mutex , MutexGuard } ;
96+ use std:: sync:: { Condvar , Mutex , MutexGuard , PoisonError } ;
9697use std:: task:: { Context , Poll } ;
9798use std:: thread;
9899use std:: time:: Duration ;
@@ -135,6 +136,48 @@ const MAX_MAX_THREADS: usize = 10000;
135136#[ cfg( not( target_family = "wasm" ) ) ]
136137const MAX_THREADS_ENV : & str = "BLOCKING_MAX_THREADS" ;
137138
139+ /// Set the maximum number of threads used by the backing thread pool.
140+ ///
141+ /// # Example
142+ ///
143+ /// ```no_run
144+ /// use blocking::unblock;
145+ /// use std::fs::{read_dir, File};
146+ /// use std::io::prelude::*;
147+ /// # use std::num::NonZeroUsize;
148+ ///
149+ /// blocking::set_max_blocking_threads(NonZeroUsize::new(100).unwrap());
150+ ///
151+ /// # fn test() -> std::io::Result<()> {
152+ /// let mut files = Vec::new();
153+ /// for entry in read_dir("/path/to/large/directory").unwrap() {
154+ /// files.push(unblock(move || -> std::io::Result<String> {
155+ /// let mut contents = String::new();
156+ /// let mut file = File::open(entry?.path())?;
157+ /// file.read_to_string(&mut contents)?;
158+ /// Ok(contents)
159+ /// }));
160+ /// }
161+ /// # Ok(())
162+ /// # }
163+ /// ```
164+ pub fn set_max_blocking_threads ( threads : NonZeroUsize ) {
165+ let executor = Executor :: get ( ) ;
166+ let mut inner = executor
167+ . inner
168+ . lock ( )
169+ . unwrap_or_else ( PoisonError :: into_inner) ;
170+ let old_limit = inner. thread_limit ;
171+ inner. thread_limit = Some ( threads) ;
172+ if let Some ( old_limit) = old_limit {
173+ // If the limit has decreased, wake up all threads to terminate those over
174+ // the new limit.
175+ if old_limit > threads {
176+ executor. cvar . notify_all ( ) ;
177+ }
178+ }
179+ }
180+
138181/// The blocking executor.
139182struct Executor {
140183 /// Inner state of the executor.
@@ -228,7 +271,7 @@ impl Executor {
228271 #[ cfg( feature = "tracing" ) ]
229272 let _span = tracing:: trace_span!( "blocking::main_loop" ) . entered ( ) ;
230273
231- let mut inner = self . inner . lock ( ) . unwrap ( ) ;
274+ let mut inner = self . inner . lock ( ) . unwrap_or_else ( PoisonError :: into_inner ) ;
232275 loop {
233276 // This thread is not idle anymore because it's going to run tasks.
234277 inner. idle_count -= 1 ;
@@ -242,7 +285,7 @@ impl Executor {
242285 panic:: catch_unwind ( || runnable. run ( ) ) . ok ( ) ;
243286
244287 // Re-lock the inner state and continue.
245- inner = self . inner . lock ( ) . unwrap ( ) ;
288+ inner = self . inner . lock ( ) . unwrap_or_else ( PoisonError :: into_inner ) ;
246289 }
247290
248291 // This thread is now becoming idle.
@@ -255,8 +298,11 @@ impl Executor {
255298 let ( lock, res) = self . cvar . wait_timeout ( inner, timeout) . unwrap ( ) ;
256299 inner = lock;
257300
258- // If there are no tasks after a while, stop this thread.
259- if res. timed_out ( ) && inner. queue . is_empty ( ) {
301+ // If there are too many threads active in the pool, stop this thread.
302+ if ( Some ( inner. thread_count ) > inner. thread_limit . map ( NonZeroUsize :: get) )
303+ // If there are no tasks after a while, stop this thread.
304+ && ( res. timed_out ( ) && inner. queue . is_empty ( ) )
305+ {
260306 inner. idle_count -= 1 ;
261307 inner. thread_count -= 1 ;
262308 break ;
@@ -272,7 +318,7 @@ impl Executor {
272318
273319 /// Schedules a runnable task for execution.
274320 fn schedule ( & ' static self , runnable : Runnable ) {
275- let mut inner = self . inner . lock ( ) . unwrap ( ) ;
321+ let mut inner = self . inner . lock ( ) . unwrap_or_else ( PoisonError :: into_inner ) ;
276322 inner. queue . push_back ( runnable) ;
277323
278324 // Notify a sleeping thread and spawn more threads if needed.
0 commit comments