1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
// Copyright (c) 2026 Mike Grier
//! 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);
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! # 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);
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! # 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.
//!
//! # Teardown drains, and why that is not a style preference
//!
//! Every teardown in this crate **drains**: it lets a callback that is already
//! queued run, rather than asking the kernel to discard it. `Drop` blocks until
//! that is finished, and [`wait::ThreadpoolWait::stop_and_drain`] is the same
//! work at a point the caller chooses.
//!
//! That is partly a correctness argument -- a queued callback is work the caller
//! asked for, and discarding it is not "finalised". It is also the only
//! available defence against a measured fault in the Windows thread pool, which
//! is worth stating plainly because the fault is silent, process-wide, and easy
//! to attribute to anything else.
//!
//! ## The fault
//!
//! Tearing down a wait asks the kernel to cancel its completion packet
//! (`IopCancelWaitCompletionPacket`). When the packet has **already been
//! delivered** to the pool's completion port, that cancel removes it. If the
//! removal lands a few microseconds after the packet was queued, on a pool that
//! has **no threads yet**, the port's notification to its worker factory can be
//! lost.
//!
//! Afterwards that pool dispatches **nothing**: not the waits already armed, not
//! a freshly armed wait, not a timer, not a completed overlapped read. The
//! factory meanwhile reads as perfectly healthy -- not paused, not shut down,
//! permitted to create a worker, no failed creation, zero workers. Work queues
//! up on the port and is never looked at.
//!
//! ## What it costs, and what recovers it
//!
//! The blast radius is one pool, and all of it. A second pool in the same
//! process is unaffected. But the pool normally affected is the **default** one,
//! so in practice that is every component in the process that did not create its
//! own -- including code with no connection to whoever tore the wait down.
//!
//! Submitting a work item recovers it immediately and completely, because that
//! reaches the factory by a route the lost notification is not on. Nothing else
//! observed does. So a program that submits work items near its waits sees a
//! **latency spike** easy to mistake for scheduler jitter; a program using only
//! waits, timers and I/O has no stimulus that will ever help it and **hangs**.
//!
//! ## Why draining is the fix rather than a delay
//!
//! A gap between the disarm and the close also prevents it, and is **not** what
//! this crate does. A gap makes the race improbable; draining makes it
//! impossible, because `WaitForThreadpoolWaitCallbacks` with
//! `fCancelPendingCallbacks` false cannot reach the removal primitive on any
//! path. The queued callback runs, dispatch clears the association, and the
//! close that follows finds nothing to take.
//!
//! Note the shape of that: **the close performs the same removal**, so no choice
//! of entry point avoids the dangerous call. Draining does not dodge it -- it
//! empties it. Avoiding
//! [`wait::ThreadpoolWait::try_cancel_pending_no_heal_tracking`] buys nothing on
//! its own, which is why the default paths drain rather than the hazardous call
//! being hidden.
//!
//! ## If you are worried about code you do not control
//!
//! The fault needs a pool with no threads, which bounds the exposure to process
//! start and to the moments after a pool's last worker retires.
//! [`pool::prewarm_default_pool`] removes that precondition for as long as the
//! pool stays warm. It is not a fix, and this crate's own teardowns do not need
//! it.
//!
//! The worker-factory counters the diagnosis turned on are **not** readable from
//! this crate: reading them needs an undocumented entry point and an unpublished
//! structure layout, which this crate does not ship. The captures that were taken
//! with them are under `measurements/`.
//!
//! Holding the pool warm permanently is not available: `SetThreadpoolThreadMinimum`
//! does not accept the default pool, and calling it that way terminates the
//! process rather than failing.
//!
//! ## Being told when a teardown had to drain: the `fail-fast` feature
//!
//! By default a teardown that finds a drain still owed records it and carries
//! on. The `fail-fast` feature, off by default, turns that into a panic, so a
//! caller who left the blocking drain to `Drop` finds out at the point it
//! happened rather than from a trace afterwards.
//!
//! **Cargo unifies features across a build, so any crate that enables this
//! turns it on for every crate in that build.** A library cannot decline
//! another dependency's choice, and `default-features = false` does not help:
//! the feature is off by default, so declining the defaults declines nothing.
//! What changes is teardown behaviour process-wide, which is a reasonable thing
//! for an application to ask about its own code and an unreasonable one to
//! impose on an unrelated component sharing the build. Enable it from a binary,
//! a test, or a development profile rather than from a published library's
//! default feature set.
//!
//! # 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.
//!
//! [`windows-overlapped-io-sys`]: https://docs.rs/windows-overlapped-io-sys
//!
//! # 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.
// Every module wraps a Win32 thread-pool object, so the whole public surface is
// gated on Windows and the crate resolves to an empty one elsewhere. This
// matches the sibling `windows-overlapped-io-sys`, and it is what lets a
// cross-platform dependency tree name this crate unconditionally instead of
// failing to compile on other targets.
pub
pub
pub
/// A trace for defects that only appear under concurrency, compiled out
/// unless the `trace` feature is on and narrowed by environment variable when
/// it is. See the module documentation for why an `eprintln!` is the wrong
/// instrument for that class of problem.
///
/// Gated on Windows like every other Win32-backed module here: the traced
/// build calls `GetCurrentThreadId`, so leaving it ungated would let
/// `--features trace` break this crate's empty-on-other-targets behaviour.
// The crate's markdown documentation is compiled as doctests, so an example that
// a contract change invalidates breaks the build instead of quietly teaching the
// old answer. `cfg(doctest)` means these items exist only while rustdoc collects
// tests, so they cost an ordinary build nothing.
;