nuclei 0.5.2

Proactive IO & runtime system
Documentation
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
407
//! Loom models of the task's state word: every race between a run, a wake,
//! the handle's await, cancel and detach, and the drops of the last
//! references.
//!
//! ```text
//! RUSTFLAGS="--cfg loom" cargo test --release --lib rt::task::loom_tests
//! ```
//!
//! Each model counts, on plain atomics loom does not track and reads only
//! after every thread joined, how often the future, the output and the
//! task's schedule (so its memory) were dropped: each exactly once. A wake
//! that is lost leaves the task unfinished, which the models assert against;
//! an awaiter that is never woken leaves its thread parked, which loom
//! reports as a deadlock.

use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize as StdAtomicUsize, Ordering as StdOrdering};
use std::task::{Context, Poll, Waker};

use loom::sync::atomic::{AtomicBool, Ordering};
use loom::sync::{Arc, Mutex};
use loom::thread;

use super::{Runnable, Schedule, ScheduleInfo, spawn};

mod last_reference;

/// A drop counter loom does not track.
#[derive(Clone)]
struct Count(std::sync::Arc<StdAtomicUsize>);

impl Count {
    fn new() -> Self {
        Self(std::sync::Arc::new(StdAtomicUsize::new(0)))
    }

    fn get(&self) -> usize {
        self.0.load(StdOrdering::SeqCst)
    }

    fn token(&self) -> Token {
        Token(self.clone())
    }
}

/// Counts one drop on its counter.
struct Token(Count);

impl Drop for Token {
    fn drop(&mut self) {
        self.0.0.fetch_add(1, StdOrdering::SeqCst);
    }
}

/// A run queue behind a loom mutex, and a drop counter for the schedule.
struct Sched {
    queue: Arc<Mutex<Vec<Runnable>>>,
    _token: Token,
    /// The calibration's broken schedule: after the first runnable, every
    /// one goes here, where nothing runs it.
    lost: Option<Arc<Mutex<Vec<Runnable>>>>,
    kept: AtomicBool,
}

impl Sched {
    fn new(queue: &Arc<Mutex<Vec<Runnable>>>, count: &Count) -> Self {
        Self {
            queue: queue.clone(),
            _token: count.token(),
            lost: None,
            kept: AtomicBool::new(false),
        }
    }
}

// SAFETY: the push happens under the lock; nothing of `self` is read after.
unsafe impl Schedule for Sched {
    fn schedule(&self, runnable: Runnable, _info: ScheduleInfo) {
        match &self.lost {
            Some(lost) if self.kept.swap(true, Ordering::AcqRel) => {
                lost.lock().unwrap().push(runnable)
            }
            _ => self.queue.lock().unwrap().push(runnable),
        }
    }
}

fn pop(queue: &Arc<Mutex<Vec<Runnable>>>) -> Option<Runnable> {
    queue.lock().unwrap().pop()
}

/// Ready once `flag` is set; otherwise stores its waker in `slot`.
struct Flagged {
    flag: Arc<AtomicBool>,
    slot: Arc<Mutex<Option<Waker>>>,
    _token: Token,
}

impl Future for Flagged {
    type Output = Token;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Token> {
        *self.slot.lock().unwrap() = Some(cx.waker().clone());
        if self.flag.load(Ordering::Acquire) {
            Poll::Ready(self._token.0.token())
        } else {
            Poll::Pending
        }
    }
}

/// Polls `future` on this loom thread, parking until its waker fires.
fn block_on<F: Future>(future: F) -> F::Output {
    struct Unpark(loom::sync::Notify);
    impl std::task::Wake for Unpark {
        fn wake(self: std::sync::Arc<Self>) {
            self.0.notify();
        }
        fn wake_by_ref(self: &std::sync::Arc<Self>) {
            self.0.notify();
        }
    }
    let park = std::sync::Arc::new(Unpark(loom::sync::Notify::new()));
    let waker = Waker::from(std::sync::Arc::clone(&park));
    let mut cx = Context::from_waker(&waker);
    let mut future = std::pin::pin!(future);
    loop {
        if let Poll::Ready(out) = future.as_mut().poll(&mut cx) {
            return out;
        }
        park.0.wait();
    }
}

/// Runs `f` under loom with at most three preemptions per schedule
/// (`LOOM_MAX_PREEMPTIONS` overrides it), and fails it if the search
/// explored fewer than `floor` interleavings: a model that cannot reach the
/// races it is about passes for the wrong reason.
fn model(name: &str, floor: usize, f: impl Fn() + Sync + Send + 'static) {
    let runs = std::sync::Arc::new(StdAtomicUsize::new(0));
    let counted = {
        let runs = runs.clone();
        move || {
            f();
            runs.fetch_add(1, StdOrdering::Relaxed);
        }
    };
    let mut builder = loom::model::Builder::new();
    builder.preemption_bound = builder.preemption_bound.or(Some(3));
    builder.check(counted);
    let explored = runs.load(StdOrdering::Relaxed);
    println!("loom model {name}: {explored} interleavings explored");
    assert!(
        explored >= floor,
        "{name}: only {explored} interleavings, below {floor}"
    );
}

/// A wake from another thread races the run: whatever the order, the task
/// is run again after the flag is set and completes. With `lose_wakes`, the
/// schedule drops every runnable after the first where nothing runs it:
/// the calibration, which must fail.
fn wake_racing_poll(lose_wakes: bool) {
    let (futures, scheds) = (Count::new(), Count::new());
    let queue = Arc::new(Mutex::new(Vec::new()));
    let lost = Arc::new(Mutex::new(Vec::new()));
    let flag = Arc::new(AtomicBool::new(false));
    let slot = Arc::new(Mutex::new(None::<Waker>));
    let future = Flagged {
        flag: flag.clone(),
        slot: slot.clone(),
        _token: futures.token(),
    };
    let mut sched = Sched::new(&queue, &scheds);
    if lose_wakes {
        sched.lost = Some(lost.clone());
    }
    let (runnable, task) = spawn(future, sched);
    runnable.schedule();

    let waker_thread = {
        let (flag, slot) = (flag.clone(), slot.clone());
        thread::spawn(move || {
            flag.store(true, Ordering::Release);
            if let Some(waker) = slot.lock().unwrap().take() {
                waker.wake();
            }
        })
    };
    // Run whatever is queued while the other thread works, then once it is
    // done: a wake it made is queued by then.
    while let Some(runnable) = pop(&queue) {
        runnable.run();
    }
    waker_thread.join().unwrap();
    while let Some(runnable) = pop(&queue) {
        runnable.run();
    }
    let finished = task.is_finished();
    // Everything goes before the verdict: a loom object dropped while a
    // failed assertion unwinds aborts the process.
    drop(task);
    lost.lock().unwrap().clear();
    slot.lock().unwrap().take();
    assert!(finished, "the wake after the flag was lost");
    assert_eq!(futures.get(), 2, "the future, and the output token it made");
    assert_eq!(scheds.get(), 1, "the memory went once");
}

#[test]
fn a_wake_racing_the_poll_is_never_lost() {
    // The floors below count the run's start as one addition and its exchanges
    // after its poll as one each: a completion or a Pending end lets go of the
    // run's reference in its exchange.
    model("a_wake_racing_the_poll_is_never_lost", 156, || {
        wake_racing_poll(false)
    });
}

/// The model above with a schedule that loses every wake after the first:
/// the search must reach an order where the task needed that wake.
#[test]
#[should_panic(expected = "the wake after the flag was lost")]
fn calibration_a_schedule_that_loses_wakes_is_caught() {
    model(
        "calibration_a_schedule_that_loses_wakes_is_caught",
        1,
        || wake_racing_poll(true),
    );
}

/// Dropping the handle on another thread races the run: the future is
/// dropped once, by whichever side owns it, and the memory goes once.
#[test]
fn a_cancel_racing_the_run_drops_the_future_once() {
    model("a_cancel_racing_the_run_drops_the_future_once", 70, || {
        let (futures, scheds) = (Count::new(), Count::new());
        let queue = Arc::new(Mutex::new(Vec::new()));
        let guard = futures.token();
        let (runnable, task) = spawn(
            async move {
                let _guard = guard;
                std::future::pending::<()>().await
            },
            Sched::new(&queue, &scheds),
        );
        let canceller = thread::spawn(move || drop(task));
        runnable.run();
        canceller.join().unwrap();
        while let Some(runnable) = pop(&queue) {
            runnable.run();
        }
        assert_eq!(futures.get(), 1);
        assert_eq!(scheds.get(), 1);
    });
}

/// The handle awaits on one thread while the task completes on another: the
/// awaiter is woken (or sees the output at once) and gets the output.
#[test]
fn an_awaiter_racing_the_completion_is_woken() {
    model("an_awaiter_racing_the_completion_is_woken", 911, || {
        let scheds = Count::new();
        let queue = Arc::new(Mutex::new(Vec::new()));
        let (runnable, task) = spawn(async { 7 }, Sched::new(&queue, &scheds));
        let runner = thread::spawn(move || {
            runnable.run();
        });
        assert_eq!(block_on(task), 7);
        runner.join().unwrap();
        assert_eq!(scheds.get(), 1);
    });
}

/// Detaching on one thread while the task completes on another: the output
/// is dropped once, by the run or by the detach.
#[test]
fn a_detach_racing_the_completion_drops_the_output_once() {
    model(
        "a_detach_racing_the_completion_drops_the_output_once",
        22,
        || {
            let (outputs, scheds) = (Count::new(), Count::new());
            let queue = Arc::new(Mutex::new(Vec::new()));
            let out = outputs.token();
            let (runnable, task) = spawn(async move { out }, Sched::new(&queue, &scheds));
            let detacher = thread::spawn(move || task.detach());
            runnable.run();
            detacher.join().unwrap();
            assert_eq!(outputs.get(), 1);
            assert_eq!(scheds.get(), 1);
        },
    );
}

/// The last waker of an idle task is dropped on one thread while the handle
/// is detached on another: whoever is last closes the task and schedules it,
/// and its run drops the future once.
#[test]
fn the_last_waker_racing_a_detach_drops_the_future_once() {
    model(
        "the_last_waker_racing_a_detach_drops_the_future_once",
        8,
        || {
            let (futures, scheds) = (Count::new(), Count::new());
            let queue = Arc::new(Mutex::new(Vec::new()));
            let slot = Arc::new(Mutex::new(None::<Waker>));
            let flag = Arc::new(AtomicBool::new(false));
            let (runnable, task) = spawn(
                Flagged {
                    flag,
                    slot: slot.clone(),
                    _token: futures.token(),
                },
                Sched::new(&queue, &scheds),
            );
            runnable.run();
            let waker = slot
                .lock()
                .unwrap()
                .take()
                .expect("the poll left its waker");
            let dropper = thread::spawn(move || drop(waker));
            task.detach();
            dropper.join().unwrap();
            while let Some(runnable) = pop(&queue) {
                runnable.run();
            }
            assert_eq!(futures.get(), 1);
            assert_eq!(scheds.get(), 1);
        },
    );
}

/// A wake by reference from another thread races the handle's drop: the
/// task is cancelled and its future dropped once, whether the wake came
/// first or not.
#[test]
fn a_wake_racing_a_cancel_drops_the_future_once() {
    model("a_wake_racing_a_cancel_drops_the_future_once", 40, || {
        let (futures, scheds) = (Count::new(), Count::new());
        let queue = Arc::new(Mutex::new(Vec::new()));
        let slot = Arc::new(Mutex::new(None::<Waker>));
        let flag = Arc::new(AtomicBool::new(false));
        let (runnable, task) = spawn(
            Flagged {
                flag,
                slot: slot.clone(),
                _token: futures.token(),
            },
            Sched::new(&queue, &scheds),
        );
        runnable.run();
        let waker = slot
            .lock()
            .unwrap()
            .take()
            .expect("the poll left its waker");
        let waking = thread::spawn(move || waker.wake_by_ref());
        drop(task);
        waking.join().unwrap();
        while let Some(runnable) = pop(&queue) {
            runnable.run();
        }
        assert_eq!(futures.get(), 1);
        assert_eq!(scheds.get(), 1);
    });
}

/// The handle is detached; another thread drops the task's only waker while
/// the run polls. Whoever lets go last (the waker's drop, closing an idle
/// task and scheduling it, or the run's own drop) drops the future, once.
#[test]
fn the_last_waker_dropped_during_the_poll_drops_the_future_once() {
    model(
        "the_last_waker_dropped_during_the_poll_drops_the_future_once",
        22,
        || {
            let (futures, scheds) = (Count::new(), Count::new());
            let queue = Arc::new(Mutex::new(Vec::new()));
            let slot = Arc::new(Mutex::new(None::<Waker>));
            let flag = Arc::new(AtomicBool::new(false));
            let (runnable, task) = spawn(
                Flagged {
                    flag,
                    slot: slot.clone(),
                    _token: futures.token(),
                },
                Sched::new(&queue, &scheds),
            );
            task.detach();
            let dropper = {
                let slot = slot.clone();
                thread::spawn(move || drop(slot.lock().unwrap().take()))
            };
            runnable.run();
            dropper.join().unwrap();
            // A waker the poll stored after the other thread looked goes now.
            drop(slot.lock().unwrap().take());
            while let Some(runnable) = pop(&queue) {
                runnable.run();
            }
            assert_eq!(futures.get(), 1, "the future was dropped once");
            assert_eq!(scheds.get(), 1, "the memory went once");
        },
    );
}