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
//! Deterministic `select!`: tokio's own expansion, driven by a seeded offset.
//!
//! This file is short on code and long on explanation, on purpose: the
//! mechanism fits in one macro rule, but it leans on how `tokio::select!`
//! is built internally. Read this doc top to bottom once and the macro at
//! the bottom will look obvious.
//!
//! ## The problem
//!
//! `select!` waits on several futures at once and runs the handler of the
//! first one that completes. When two branches become ready at the same
//! moment, SOMETHING has to decide which handler runs. In tokio that
//! decision is the polling order: every time the select future is polled,
//! tokio picks a start index and polls the branches in the order
//! `start, start+1, ... (mod N)`. The first branch found ready wins.
//!
//! The start index is random so that no branch can starve the others. That
//! randomness comes from tokio's thread-local RNG (`thread_rng_n`), which
//! is:
//!
//! - seedable only through the unstable `tokio_unstable` runtime API, and
//! - seeded from process entropy when there is no tokio runtime at all,
//! which is exactly the moonpool situation: there is NO tokio runtime
//! inside a simulation.
//!
//! For a simulation that must replay a seed bit-for-bit this is a
//! correctness hole: the same seed would pick different branch winners on
//! every run of the test binary.
//!
//! ## How tokio's macro is built (the part we reuse)
//!
//! `tokio::select!` is a `macro_rules!` macro organized as a three-stage
//! pipeline (see tokio's `src/macros/select.rs`):
//!
//! ```text
//! stage 1: entry rules recognize the overall shape of the call
//! stage 2: normalize rules munch the branches one by one (tagged with @)
//! stage 3: transform rule emit the real code: the polling loop
//! ```
//!
//! The line that matters is tokio's own fair-mode ENTRY rule (abridged):
//!
//! ```text
//! ($p:pat = $($t:tt)*) => {
//! $crate::select!(@{ start={ thread_rng_n(BRANCHES) }; () } $p = $($t)*)
//! };
//! ```
//!
//! Notice what it does NOT do: it does not call the RNG. Macros only move
//! tokens around; the RNG call is written, unevaluated, into a slot named
//! `start`, and everything is forwarded to stage 2. Stages 2 and 3 carry
//! the slot along untouched (they match it as `start=$start:expr`), and
//! stage 3 finally pastes it INSIDE the polling closure it emits:
//!
//! ```text
//! poll_fn(|cx| {
//! // ...
//! let start = $start; // evaluated HERE, on EVERY poll
//! for i in 0..BRANCHES {
//! let branch = (start + i) % BRANCHES;
//! // ... poll that branch, first Ready wins ...
//! }
//! })
//! ```
//!
//! So the randomness source is not baked into the machinery at all. It is
//! a plug-in expression chosen by whoever writes the entry rule. tokio's
//! entry rule plugs in `thread_rng_n(BRANCHES)`. Nothing stops a different
//! entry rule from plugging in something else, because `macro_rules!` has
//! no private rules: the stage-2 and stage-3 rules live in the same
//! `#[macro_export]`ed `select!` macro, so `tokio::select!(@{ ... } ...)`
//! is callable from any crate.
//!
//! ## What moonpool's `select!` does
//!
//! The macro below IS that different entry rule. For the default (fair)
//! form it jumps straight into tokio's stage 2, plugging moonpool's seeded
//! source into the `start` slot:
//!
//! ```text
//! tokio::select!(@{ start={ select_support::select_offset(BRANCHES) }; () } ...)
//! ```
//!
//! Everything after the entry rule is tokio's machinery, byte for byte:
//! branch polling, the disabled-branch mask, `if` preconditions, refutable
//! patterns, `else`, cooperative budgeting, the 64-branch cap, source-order
//! evaluation of the future expressions, and the per-poll redraw of the
//! offset. We copied none of it and we maintain none of it. The ONLY thing
//! that changes is where the number comes from.
//!
//! ## Why the `BRANCHES` token resolves (macro hygiene)
//!
//! `BRANCHES` is a `const` holding the branch count, defined by stage 3
//! inside the block it emits. Our entry rule writes the token `BRANCHES`
//! into the `start` expression BEFORE that const exists anywhere. Two
//! macro-hygiene facts make this sound:
//!
//! - `macro_rules!` hygiene only isolates local variables (`let` bindings
//! and labels). Items are not isolated, and a `const` is an item: any
//! code that ends up inside its scope can name it, no matter which macro
//! wrote which token.
//! - tokio itself depends on exactly this. Its own entry rule writes the
//! `BRANCHES` token in one macro expansion, and the const is defined by
//! a later, separate expansion (stage 3). Our token rides through the
//! identical path, so it resolves the same way tokio's own token does.
//!
//! ## Where the offset comes from at runtime
//!
//! [`select_support::select_offset`](crate::select_support) is the number
//! source:
//!
//! - **Simulation**: moonpool-sim installs a seeded stream per iteration
//! ([`set_select_offset_override`](crate::select_support::set_select_offset_override)),
//! so the same seed replays the same winners exactly and different seeds
//! explore different orderings.
//! - **Production** (no override installed): a thread-local entropy-seeded
//! RNG, behaviorally what tokio's fair mode does.
//!
//! ## The internal-format dependency (the price)
//!
//! The `@{ start=...; () }` entry shape is tokio's `#[doc(hidden)]` macro
//! plumbing, not covered by semver. Two facts make that acceptable:
//!
//! - The shape has been stable since tokio 1.4 (2021, when `biased;`
//! landed) and is identical in every release since.
//! - The failure mode is loud. If a future tokio changes the shape, every
//! fair-mode `select!` call site stops compiling with a macro-match error
//! on the upgrade commit. It can not drift silently; winner-selection and
//! determinism tests pin the semantics on top.
//!
//! If it ever breaks, the fallbacks are to vendor tokio's `select.rs` or to
//! resurrect the rotation combinator this macro replaced (git history).
//!
//! ## The build-vs-test asymmetry
//!
//! Which macro you get is decided by cargo feature unification, not by the
//! code you compile. Without `deterministic-select` this crate re-exports
//! tokio's `select!` verbatim. The moment moonpool-sim enters the build
//! graph, even as a dev-dependency, this macro replaces it for EVERY crate
//! in that graph, including your library code compiled for tests. Both forms
//! are tokio's own expansion with identical grammar and limits, so the swap
//! changes nothing but the offset source.
/// Waits on multiple concurrent branches, returning when the **first**
/// completes, with a deterministic, seed-controlled polling order.
///
/// Drop-in replacement for [`tokio::select!`] with the same grammar:
///
/// ```text
/// select! {
/// <pattern> = <async expression> (, if <precondition>)? => <handler>,
/// ...
/// (else => <handler>)?
/// }
/// ```
///
/// By default the branch polled first is chosen, on every poll, by an offset
/// drawn from [`select_support::select_offset`](crate::select_support):
/// inside a moonpool simulation that source is seeded (same seed, same
/// choices, exact replay; different seeds explore different orderings), and
/// outside a simulation it is entropy-based, matching tokio's production
/// behavior.
///
/// `select! { biased; ... }` skips the draw entirely and forwards verbatim to
/// `tokio::select! { biased; ... }`: branches are polled top to bottom, which
/// is already fully deterministic. Use it when branches have a natural
/// priority (shutdown or timeout guards); use the default form when branches
/// are peers racing for the same wake (message queues, notify channels), so
/// the simulation can explore both winners across seeds.
///
/// # Examples
///
/// ```
/// # futures::executor::block_on(async {
/// let winner = moonpool_core::select! {
/// x = async { 1 } => x,
/// _ = std::future::pending::<()>() => unreachable!(),
/// };
/// assert_eq!(winner, 1);
/// # });
/// ```
///
/// See the [module docs](crate::select) for the full walkthrough of how the
/// offset is injected into tokio's own expansion.