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
//! Blocking work on a pool of threads, and [`Unblock`], an adapter for blocking I/O handles.
//!
//! [`unblock()`] runs a closure on a pool of threads and returns a future of its result.
//! [`Unblock`] gives a blocking I/O handle (a file, the standard input, an iterator) async read,
//! write, seek and stream traits by running each operation on it as blocking work.
//!
//! Neither needs a runtime. Both work under any executor.
pub
use ;
pub use Unblock;
use Pool;
/// Runs `work` on a thread of a pool kept for blocking work, and returns a future of its result.
///
/// Use it for work that blocks, such as a file read, a host-name lookup or a call into a library
/// with no async form. If such work runs on the thread that polls tasks, it holds up every other
/// task on that thread. Here it holds up only the pool thread that runs it. The task that awaits
/// the future is woken when the work is done.
///
/// The work is submitted to the pool before `unblock` returns, not when the future is first polled.
/// An idle pool thread takes it. If none is idle, a new thread is started, up to a limit of 500
/// threads. Beyond that, the work waits in a queue, in submission order, until a thread is free.
///
/// Pool threads are named `zruntime blocking work`. A thread is reused for later work. It exits
/// after ten seconds with nothing to run, so an unused pool holds no threads.
///
/// Work that never ends holds its thread forever. If every pool thread is held that way, later work
/// waits forever too. The pool suits work that ends on its own. For work that waits on other pool
/// work, or that runs as long as the program does, use [`std::thread::spawn`] instead.
///
/// The work runs to the end whether or not the future is polled, and even if the future is dropped.
/// Dropping the future gives up the wait for the result. It cannot cancel the work, or remove it
/// from the queue before it starts. Nothing waits for the pool threads, so work that is still
/// running or queued when the process exits is lost.
///
/// The returned future needs no runtime. It wakes the waker from its most recent poll, so it can be
/// awaited under any executor, from a task on any thread.
///
/// # Panics
///
/// If `work` panics, the panic is caught on the pool thread, which carries on with other work. The
/// poll that would have returned the value raises the panic again, with its original payload. A
/// future that is dropped before that poll never sees the panic. The panic hook runs once, when the
/// panic happens, and not again when the panic is raised again.
///
/// `unblock` itself panics if the pool has no thread and cannot start one for the work, as
/// [`std::thread::spawn`] does when it fails. If the pool has threads, the work waits for one of
/// them instead.
///
/// # Example
///
/// A sleep stands in for blocking work. The example drives the future with `block_on` from the
/// `futures` crate, but the `block_on` of any executor works, as `unblock` needs no runtime:
///
/// ```
/// use std::{thread, time::Duration};
///
/// use futures::executor::block_on;
/// use zruntime::unblock;
///
/// let answer = unblock(|| {
/// thread::sleep(Duration::from_millis(10));
/// 42
/// });
///
/// assert_eq!(block_on(answer), 42);
/// ```
/// The future returned by [`unblock()`]. It resolves to the value the work returned.
///
/// It needs no runtime and works under any executor. It is `Send` and `Sync` if the value is
/// `Send`, so a task that awaits it can move between threads.
///
/// Dropping it before it resolves gives up the wait, not the work. The pool still runs the work to
/// the end, in its turn if no thread has started it yet. Once the future is dropped or has
/// resolved, the work no longer keeps the awaiting task or its executor alive.
;
/// Runs `work` as [`unblock()`] does, but on a thread of `pool` rather than of the pool that
/// `unblock()` hands its work to, and hands back a future of what it returns.
///
/// All that `unblock()` says of the work and of its future holds here with `pool` in place of that
/// pool: the work is handed to `pool` before this returns, and waits its turn there once `pool` has
/// as many threads as its cap allows.
///
/// # Panics
///
/// Panics if `pool` has no thread at all and cannot start one for the work, as [`unblock()`] does.
pub
/// The pool that [`unblock()`] hands work to. Its threads are named `zruntime blocking work`, as
/// the documentation of `unblock()` says.
static POOL: Pool = new;
/// The most threads a pool has at once, [`POOL`] and the pool that the waits for child processes
/// run on alike: as many as the pool of smol's `blocking` crate has by default. That is room for a
/// burst of slow lookups or file reads to run side by side, or for a burst of children to be waited
/// for at once, while a flood of work that blocks for good queues up rather than starting threads
/// without end.
pub const MAX_THREADS: NonZeroUsize = new.unwrap;
/// How long a thread of a pool waits for more work before it ends, whichever pool it is: as long
/// as tokio keeps an idle thread of its own pool for blocking work. That spans the gaps in a
/// steady stream of work, and lets the threads that a burst of it started go soon after the burst
/// is over.
pub const IDLE_TIMEOUT: Duration = from_secs;
/// Under Miri, a thread of a pool ends as soon as it finds no work to run. Miri ends a program
/// with an error if a thread other than the main one is still running as the main one returns, and
/// a thread that waited for more work would be.
pub const IDLE_TIMEOUT: Duration = ZERO;
/// Where the thread leaves the outcome of the work, and the waker it hands back to.
///
/// Both live behind the one lock, so that seeing the outcome and taking the waker to wake it are
/// never two separate steps from the other side's point of view: see [`Finish`] for what that
/// buys.
/// What the work returned, or what it panicked with.
type Outcome<T> = ;
/// Wakes whoever is waiting for the outcome as the work ends, before letting go of the lock the
/// waiting side has to take to see that outcome.
///
/// While this drop holds [`State`]'s lock, [`BlockingWork`] cannot take it and so cannot see the
/// outcome stored under it; by the time it can, the waker this drop took out has been woken and,
/// with nothing else left holding it, dropped. A poll that reaches the lock first, in the gap
/// between the outcome being stored and this drop running, takes the waker itself instead,
/// leaving this drop nothing to wake. Either way, the awaiting task can only complete once the
/// thread of the work holds nothing that came from the runtime that polled it, and that runtime is
/// then free to be torn down with nothing of it left on that thread.
///
/// Waking while still holding the lock cannot deadlock here: the lock is private to this
/// hand-over, reachable from nowhere but the job that [`unblock()`] hands the pool and the future
/// it wakes. A `wake` that drops that future, as an executor may with a task it can no longer run,
/// finds the lock taken and leaves it be, the waker having been taken out already: see
/// [`BlockingWork`]'s drop.
;
/// The state behind its lock, taken whether or not a panic poisoned it.
/// Drops the payload of a panic that is to go no further, with a panic of its destructor caught.
///
/// The payload is somebody else's value, and its `Drop` may panic in turn. The payload of such a
/// second panic is dropped too, the same way, and only one that panics a third time is leaked, so
/// as not to follow a chain of destructors that each panic. `Event` has a function like this one
/// too, but this module does not use it: the `event` feature may be left out of the build, and
/// blocking work needs none of it.