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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// Copyright (c) 2026 Mike Grier
//! Owned private thread pools: `CreateThreadpool` / `CloseThreadpool`.
//!
//! Callbacks run on the process-default pool unless a [`CallbackEnviron`] names
//! a private one. A private pool lets an application bound the threads a
//! subsystem may consume, or isolate long-running callbacks from unrelated work,
//! without affecting the rest of the process.
//!
//! [`CallbackEnviron`]: crate::callback_env::CallbackEnviron
use io;
use ptr;
use Mutex;
use ;
/// The thread limits this wrapper has been told about.
///
/// Each field is `None` until the corresponding setter succeeds, because Win32
/// offers no way to read a pool's current limits back. A limit we were never
/// given cannot be used to reject its counterpart.
/// An owned private thread pool.
///
/// Pass it to [`CallbackEnviron::set_pool`] to run an object's callbacks on this
/// pool instead of the process-default one. The environment borrows the pool, so
/// the pool cannot be closed while an environment still names it.
///
/// # Ordering and teardown
///
/// Creating a callback object from an environment that names this pool **binds**
/// the object to the pool. `CloseThreadpool` -- which this type's `Drop` calls --
/// frees the pool immediately only when no object is bound; otherwise it defers
/// the release until every bound object has been freed. A live object can
/// therefore never observe a freed pool, whatever order the pool and its objects
/// are dropped in, so this is not a memory-safety obligation on the caller. (The
/// `CallbackEnviron` borrow of the pool covers the one case binding does not: a
/// freshly created pool with no bound object yet is freed at once, so an
/// environment must not outlive it.)
///
/// What the order *does* control is when teardown blocks. Each object's `Drop`
/// waits for its in-flight callbacks; the pool's deferred release then completes
/// once the last object is gone. Declare the pool before the objects that use
/// it, so it is dropped last and that blocking happens where you expect.
///
/// # Examples
///
/// ```
/// use windows_threadpool_sys::callback_env::CallbackEnviron;
/// use windows_threadpool_sys::pool::ThreadpoolPool;
/// use windows_threadpool_sys::timer::ThreadpoolTimer;
/// use std::time::Duration;
///
/// // Declared first, so it outlives the objects that use it.
/// let pool = ThreadpoolPool::new()?;
/// pool.set_min_threads(1)?;
/// pool.set_max_threads(4)?;
///
/// let mut env = CallbackEnviron::new();
/// env.set_pool(&pool);
///
/// let timer = ThreadpoolTimer::new(|_firing| {}, Some(&mut env))?;
/// timer.set_after(Duration::from_millis(1));
/// // Discharges the drain this timer owes, at a point you choose rather than
/// // leaving the blocking teardown to `Drop`.
/// timer.stop_and_drain();
/// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`CallbackEnviron::set_pool`]: crate::callback_env::CallbackEnviron::set_pool
// SAFETY: PTP_POOL is a kernel-managed object usable from any thread; this type
// only owns the handle and hands it to the pool APIs, which are thread-safe.
unsafe
unsafe
/// Make the default process pool create its first worker, and block until one
/// exists.
///
/// # Why this exists
///
/// A measured fault in the Windows thread pool needs a pool holding **zero**
/// threads. A teardown that removes an already-delivered completion packet a few
/// microseconds after it was queued can sever that port's notification to its
/// worker factory, after which the pool dispatches nothing -- no wait, no timer,
/// no I/O completion -- until something submits a work item. The factory reads as
/// perfectly idle throughout: not paused, not shut down, permitted to create,
/// nothing failed.
///
/// Warming the pool first was measured to prevent it: **0 occurrences in 24000
/// runs warm, against 66 in 24000 cold**, with a delay-matched cold control still
/// failing at the cold rate, so it is the worker and not the elapsed time.
///
/// # What it does not buy
///
/// **This is not a fix and does not make a bad teardown safe.** It removes one
/// of the fault's preconditions for as long as the pool stays warm, and the pool
/// stops being warm once it has been idle for its timeout -- 67 seconds on the
/// machine this was measured on. A long-lived process that goes quiet becomes
/// cold again, and this function would have to be called again to matter.
///
/// It does nothing about teardowns in this crate, which already drain and so
/// never remove a delivered packet. Its value is against code you do not
/// control: another library, or a dependency, tearing a wait down badly during
/// your process's startup.
///
/// # It is opt-in, and this crate never calls it for you
///
/// Nothing in this crate warms the pool on your behalf -- not `ThreadpoolWait::new`,
/// not any other constructor. Three reasons, in order of weight:
///
/// 1. **It is a process-wide side effect.** A thread created here belongs to the
/// default pool, which is shared with every other component in the process. A
/// library that silently adds a resident thread to a pool it does not own has
/// made a decision that was not its to make.
/// 2. **This crate's own teardowns do not need it.** They drain, so they never
/// remove a delivered packet. Calling this automatically would protect against
/// *other* code while implying that this crate's paths required it.
/// 3. **It would be a surprising cost in the common case.** Most callers create a
/// wait and never tear it down badly; charging all of them a thread for a
/// hazard they do not have is the wrong default.
///
/// Whether the protection is worth one resident thread is a judgement about the
/// process as a whole, which the caller is in a position to make and this crate
/// is not.
///
/// # What was ruled out
///
/// Holding the pool warm *permanently* would be better, and is not available:
/// `SetThreadpoolThreadMinimum` does not accept a null pool, and calling it that
/// way does not fail -- it **raises `STATUS_INVALID_PARAMETER` and terminates the
/// process**. The default pool's minimum cannot be set. A private
/// [`ThreadpoolPool`] can have [`set_min_threads`](ThreadpoolPool::set_min_threads)
/// applied to it, but that is a different pool.
///
/// # It always submits, and does not check first
///
/// The obvious optimisation is to read the worker count and skip the work item
/// when the pool is already warm. It is not done, for two reasons, and the first
/// is simply that it is slower. Measured on the development machine: prewarming
/// an already-warm pool costs about 27us, while the query needed to decide to
/// skip it costs about 204us, because there is no way to ask "how many workers
/// does the default pool have" without scanning the handle space. The skip would
/// cost roughly seven times what it saves.
///
/// The second reason outlasts the first, and has since become the whole of it.
/// That query reads a layout Microsoft does not publish, through an undocumented
/// entry point. A misread returning a plausible non-zero would make this function
/// skip the warm-up and report success, leaving the caller believing they are
/// protected when they are not -- the exact failure this exists to prevent. An
/// unconditional submit cannot be wrong that way.
///
/// **This crate no longer contains any way to make that query.** The facility
/// that did was removed rather than kept behind a feature; see the decision in
/// the workspace `DESIGN-NOTES.md`. A caller who wants worker counts needs a
/// mechanism built on documented ground, such as an ETW kernel trace.
///
/// # Returns
///
/// Whether a worker is confirmed to exist. A callback having run is the proof,
/// because it ran on one. `false` means the work item could not be created or did
/// not run within the bound, and the pool should be assumed cold.