Expand description
Memory-safe access to the Windows thread pool APIs.
The Windows thread pool integrates work, timers, waits, and asynchronous I/O with the operating system’s own scheduling facilities. Its distinguishing property is that an idle workload costs no threads at all: the pool and the kernel cooperate so a process waiting on timers, events, or I/O holds no dedicated thread stacks. This crate wraps those facilities while making callback and resource lifetimes explicit in Rust.
§The object types
Each thread-pool object is an owned Rust type whose Drop performs the
documented teardown for that object, so callbacks can never outlive the state
they capture:
| Type | Wraps | Runs the callback when |
|---|---|---|
work::ThreadpoolWork | TP_WORK | you submit it |
timer::ThreadpoolTimer | TP_TIMER | a due time arrives, once per arming |
timer::ThreadpoolPeriodicTimer | TP_TIMER | every period, until stopped |
wait::ThreadpoolWait | TP_WAIT | a handle signals or a wait times out |
io::ThreadpoolIo | TP_IO | an overlapped operation completes |
One-shot and periodic timers are separate types on purpose. The platform
models both with one object and a period argument, which hides the property
that matters most when writing the callback: a timer::ThreadpoolPeriodicTimer may
queue its next tick while the previous one is still running, so its callback
must tolerate overlapping with itself, whereas a timer::ThreadpoolTimer
re-armed from inside its callback never does – that request is applied
only once the callback returns. Arming a one-shot from outside while its
callback runs can still overlap it; see the timer module for both the
choice and that distinction.
Three supporting types shape where those callbacks run and how they are torn
down: pool::ThreadpoolPool is an owned private pool,
callback_env::CallbackEnviron is the environment that selects a pool and
a callback priority when an object is created, and
cleanup_group::CleanupGroup releases many objects in one step instead of
dropping each individually.
§Submitting work
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use windows_threadpool_sys::work::ThreadpoolWork;
let count = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&count);
let work = ThreadpoolWork::new(move || {
counter.fetch_add(1, Ordering::SeqCst);
}, None)?;
for _ in 0..4 {
work.submit();
}
work.wait();
assert_eq!(count.load(Ordering::SeqCst), 4);§Running callbacks on a private pool
A pool::ThreadpoolPool bounds the threads a subsystem may consume.
Declare the pool before the objects that use it, so it is dropped last.
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use windows_threadpool_sys::callback_env::CallbackEnviron;
use windows_threadpool_sys::pool::ThreadpoolPool;
use windows_threadpool_sys::work::ThreadpoolWork;
let pool = ThreadpoolPool::new()?;
pool.set_max_threads(2)?;
let mut env = CallbackEnviron::new();
env.set_pool(&pool);
let count = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&count);
let work = ThreadpoolWork::new(move || {
counter.fetch_add(1, Ordering::SeqCst);
}, Some(&mut env))?;
work.submit();
work.wait();
assert_eq!(count.load(Ordering::SeqCst), 1);§Callback rules
Callbacks run on shared, process-managed threads, so every object type here holds its callback to the same contract:
- It must restore any thread-local or thread state it changes before returning, and must not terminate its thread.
- It must not block waiting on its own object’s rundown, which would wait on itself.
- It must not panic. A panic unwinds to the
extern "system"trampoline, where an escaping unwind aborts the process; nothing contains it. The panic hook still runs first, so the message and location reach stderr by default – what is given up is the process, not the diagnostic. A callback that can fail must handle its own errors rather than panicking.
§Relationship to windows-overlapped-io-sys
Thread-pool I/O is one of three completion backends for the overlapped model
defined by windows-overlapped-io-sys. This crate implements the TP_IO
backend over that crate’s endpoint ownership and pinned operation storage,
adding the balanced StartThreadpoolIo accounting that only the thread pool
requires. The pool’s internal completion port is never exposed.
§Status
The crate is in active development. Work, timers, waits, private pools, cleanup groups, and thread-pool I/O are implemented and tested.
Thread-pool I/O is deliberately not a cleanup-group member: a TP_IO object
must not be closed while an overlapped operation is outstanding, and a bulk
release cannot satisfy that. See cleanup_group for the reasoning.
Modules§
- callback_
env - SDK-equivalent helpers for [
TP_CALLBACK_ENVIRON_V3]. - cleanup_
group - Cleanup groups: releasing many thread-pool objects in one step.
- io
- Thread-pool I/O (
TP_IO): a completion backend over the shared overlapped submission seam. - pool
- Owned private thread pools:
CreateThreadpool/CloseThreadpool. - timer
- Thread-pool timers:
CreateThreadpoolTimer/SetThreadpoolTimer/WaitForThreadpoolTimerCallbacks/CloseThreadpoolTimer. - wait
- Thread-pool waits:
CreateThreadpoolWait/SetThreadpoolWait/WaitForThreadpoolWaitCallbacks/CloseThreadpoolWait. - work
- Thread-pool work objects:
CreateThreadpoolWork/SubmitThreadpoolWork/WaitForThreadpoolWorkCallbacks/CloseThreadpoolWork.