Skip to content

Commit 6f41360

Browse files
authored
Support setting the number of threads without an env var (#75)
1 parent 60d526b commit 6f41360

1 file changed

Lines changed: 54 additions & 8 deletions

File tree

src/lib.rs

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
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;
9293
use std::panic;
9394
use std::pin::Pin;
9495
use std::sync::atomic::{AtomicUsize, Ordering};
95-
use std::sync::{Condvar, Mutex, MutexGuard};
96+
use std::sync::{Condvar, Mutex, MutexGuard, PoisonError};
9697
use std::task::{Context, Poll};
9798
use std::thread;
9899
use std::time::Duration;
@@ -135,6 +136,48 @@ const MAX_MAX_THREADS: usize = 10000;
135136
#[cfg(not(target_family = "wasm"))]
136137
const 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.
139182
struct 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

Comments
 (0)