interweave 0.1.0

Stateless model checker for concurrent programs: watch Optimal DPOR explore interleavings of async processes on a deterministic from-scratch executor.
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
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
//! An unbounded MPSC channel whose send / recv operations are `.await` yield points.
//!
//! [`Sender`] (cloneable, multi-producer) and [`Receiver`] (`!Clone`, single-consumer) are handles
//! to one shared [`Channel`]. Every operation registers itself on its first poll and yields. Each
//! commit is a distinct scheduling point with a [`Transition`].
//!
//! A `recv` registered against an empty queue is withheld from [`enabled`](Object::enabled) — that
//! is how the consumer blocks, and `State::settle` turns a live consumer with nothing enabled into
//! a deadlock.

use std::{
    cell::{Cell, RefCell},
    collections::VecDeque,
    fmt::Debug,
    future::poll_fn,
    rc::Rc,
    task::{Poll, Waker},
};

use crate::model::{Object, ObjectID, Transition};

// A send carries its value (moved out by the commit) and a flag the commit sets to release the
// sender; a recv carries the slot the commit writes the popped value into.
enum Op<T> {
    Send { value: T, done: Rc<Cell<bool>> },
    Recv { slot: Rc<Cell<Option<T>>> },
}

// The op kind resolved for dependency tests, carrying the recv's consumed send-seq (`None` for a
// still-pending recv, which has not consumed anything yet).
enum Kind {
    Send,
    Recv { consumed: Option<usize> },
}

struct Request<T> {
    transition: Transition,
    waker: Waker,
    op: Op<T>,
}

// `value` is rendered at commit time (purely for `label`; the value itself moves through the
// queue and is otherwise not kept).
enum Record {
    Send { value: String },
    Recv { consumed: usize, value: String },
}

struct Channel<T> {
    id: ObjectID,
    seq: usize,
    // The single consumer's process id, fixed by the first `recv` registration. The
    // `(Recv, Recv)` independence in `depends` is sound only with one consumer, so a
    // second one is rejected (see `register`).
    consumer: Option<usize>,
    // Committed-but-unreceived messages: (producing send's seq, value).
    queue: VecDeque<(usize, T)>,
    requests: Vec<Request<T>>,
    history: Vec<(Transition, Record)>,
}

impl<T: Debug> Channel<T> {
    fn new(id: ObjectID) -> Self {
        Self {
            id,
            seq: 0,
            consumer: None,
            queue: VecDeque::new(),
            requests: Vec::new(),
            history: Vec::new(),
        }
    }

    fn register(&mut self, op: Op<T>, waker: Waker) {
        let transition = Transition::new(self.id, self.seq);
        // Enforce the single-consumer invariant `depends` relies on: `recv` takes
        // `&self`, so the type system cannot stop a shared `Receiver` (e.g. `Rc`) from
        // recv-ing on two processes, which would make recv order observable and could
        // hide reachable interleavings. Reject the second consumer loudly.
        if matches!(op, Op::Recv { .. }) {
            match self.consumer {
                None => self.consumer = Some(transition.pid),
                Some(c) => assert_eq!(
                    c, transition.pid,
                    "an MPSC channel has a single consumer; a Receiver must not be shared across processes"
                ),
            }
        }
        self.seq += 1;
        self.requests.push(Request {
            transition,
            waker,
            op,
        });
    }

    // Only the committing op's process is woken — a blocked recv becomes selectable through
    // `enabled` reading the queue, not by being re-queued in the executor (symmetric with
    // `Atomic::apply`).
    fn apply(&mut self, t: Transition) {
        let Some(i) = self.requests.iter().position(|r| r.transition == t) else {
            panic!("transition must be enabled");
        };
        let req = self.requests.remove(i);
        match req.op {
            Op::Send { value, done } => {
                self.history.push((
                    t,
                    Record::Send {
                        value: format!("{value:?}"),
                    },
                ));
                self.queue.push_back((t.seq, value));
                done.set(true);
            }
            Op::Recv { slot } => {
                let (send_seq, value) = self.queue.pop_front().expect("recv must be enabled");
                self.history.push((
                    t,
                    Record::Recv {
                        consumed: send_seq,
                        value: format!("{value:?}"),
                    },
                ));
                slot.set(Some(value));
            }
        }
        req.waker.wake();
    }

    // Sends never block; a recv blocks while the queue is empty. Insertion order is fixed for
    // replay determinism.
    fn enabled(&self) -> Vec<Transition> {
        let mut out = Vec::new();
        self.enabled_into(&mut out);
        out
    }

    fn enabled_into(&self, out: &mut Vec<Transition>) {
        let queue_nonempty = !self.queue.is_empty();
        out.extend(
            self.requests
                .iter()
                .filter(|r| match r.op {
                    Op::Send { .. } => true,
                    Op::Recv { .. } => queue_nonempty,
                })
                .map(|r| r.transition),
        );
    }

    // Resolves a transition's kind whether still pending or already committed; DPOR asks about a
    // past transition (in `history`) against a process's next op (still in `requests`).
    fn kind_of(&self, t: Transition) -> Kind {
        if let Some(req) = self.requests.iter().find(|r| r.transition == t) {
            return match req.op {
                Op::Send { .. } => Kind::Send,
                Op::Recv { .. } => Kind::Recv { consumed: None },
            };
        }
        let (_, rec) = self
            .history
            .iter()
            .find(|(tt, _)| *tt == t)
            .expect("transition not registered on this channel");
        match rec {
            Record::Send { .. } => Kind::Send,
            Record::Recv { consumed, .. } => Kind::Recv {
                consumed: Some(*consumed),
            },
        }
    }

    // Two sends into one FIFO are dependent (the single consumer reads them in enqueue order, so
    // the order is observable). A send and a recv are dependent only when the recv consumed *this*
    // send (the causal send→recv edge); a concurrent send appended behind the popped element
    // commutes with the recv. Two recvs share the one consumer (program order handles them), and a
    // pending recv has consumed nothing yet, so it is independent.
    fn depends(&self, t1: Transition, t2: Transition) -> bool {
        match (self.kind_of(t1), self.kind_of(t2)) {
            (Kind::Send, Kind::Send) => true,
            (Kind::Recv { .. }, Kind::Recv { .. }) => false,
            (Kind::Send, Kind::Recv { consumed }) => consumed == Some(t1.seq),
            (Kind::Recv { consumed }, Kind::Send) => consumed == Some(t2.seq),
        }
    }

    fn label(&self, t: Transition) -> String {
        let (_, rec) = self
            .history
            .iter()
            .find(|(tt, _)| *tt == t)
            .expect("label called on an unapplied transition");
        match rec {
            Record::Send { value } => format!("send {value}"),
            Record::Recv { consumed, value } => format!("recv -> {value} (#{consumed})"),
        }
    }
}

// The internal `Object` the world drives. `Sender`/`Receiver` share the same `Rc`, but they are not
// `Clone`-symmetric (a `Receiver` must not be cloneable), and `World::register` needs a
// `Clone + Object` handle — this is that handle.
pub(crate) struct ChannelHandle<T> {
    chan: Rc<RefCell<Channel<T>>>,
}

impl<T> Clone for ChannelHandle<T> {
    fn clone(&self) -> Self {
        Self {
            chan: Rc::clone(&self.chan),
        }
    }
}

impl<T: Debug> ChannelHandle<T> {
    pub(crate) fn new(id: ObjectID) -> Self {
        Self {
            chan: Rc::new(RefCell::new(Channel::new(id))),
        }
    }

    // The shared state, for splitting the driver into producer/consumer halves.
    pub(crate) fn split(&self) -> (Sender<T>, Receiver<T>) {
        let chan = Rc::clone(&self.chan);
        (
            Sender {
                chan: Rc::clone(&chan),
            },
            Receiver { chan },
        )
    }
}

impl<T: Debug + 'static> Object for ChannelHandle<T> {
    fn apply(&mut self, t: Transition) {
        self.chan.borrow_mut().apply(t);
    }

    fn enabled(&self) -> Vec<Transition> {
        self.chan.borrow().enabled()
    }

    fn enabled_into(&self, out: &mut Vec<Transition>) {
        self.chan.borrow().enabled_into(out);
    }

    fn label(&self, t: Transition) -> String {
        self.chan.borrow().label(t)
    }

    fn depends(&self, t1: Transition, t2: Transition) -> bool {
        self.chan.borrow().depends(t1, t2)
    }
}

/// The sending half of an MPSC channel; cloneable, so several producers can share it.
///
/// [`send`](Sender::send) is an `async` method: awaiting it registers the send and yields at a
/// scheduling point.
pub struct Sender<T> {
    chan: Rc<RefCell<Channel<T>>>,
}

impl<T> Clone for Sender<T> {
    fn clone(&self) -> Self {
        Self {
            chan: Rc::clone(&self.chan),
        }
    }
}

impl<T> std::fmt::Debug for Sender<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // `try_borrow` so printing a handle never panics, even from inside an `apply`
        // that formats a self-referential message holding this sender.
        match self.chan.try_borrow() {
            Ok(c) => f
                .debug_struct("Sender")
                .field("channel", &c.id)
                .finish_non_exhaustive(),
            Err(_) => f.debug_struct("Sender").finish_non_exhaustive(),
        }
    }
}

impl<T: Debug> Sender<T> {
    /// Enqueues `value` at the back of the channel.
    ///
    /// Awaiting this is a scheduling point: the value is enqueued when this send commits. Always
    /// succeeds — the channel is unbounded.
    pub async fn send(&self, value: T) {
        // `pending.take()` registers at most once, so a spurious re-poll before the commit
        // re-yields instead of registering a second send.
        let done = Rc::new(Cell::new(false));
        let mut pending = Some(value);
        poll_fn(move |cx| {
            if done.get() {
                return Poll::Ready(());
            }
            if let Some(value) = pending.take() {
                self.chan.borrow_mut().register(
                    Op::Send {
                        value,
                        done: Rc::clone(&done),
                    },
                    cx.waker().clone(),
                );
            }
            Poll::Pending
        })
        .await
    }
}

/// The receiving half of an MPSC channel. It is intentionally **not** `Clone`, because the
/// channel's dependency relation assumes a single consumer.
///
/// That single-consumer invariant is a contract you must uphold, not one the type system can
/// guarantee: [`recv`](Receiver::recv) takes `&self`, so nothing stops you from sharing one
/// `Receiver` across processes (for instance behind an `Rc`). Doing so is rejected at run time
/// with a panic.
///
/// [`recv`](Receiver::recv) is an `async` method: awaiting it registers the recv and yields,
/// blocking while the channel is empty.
pub struct Receiver<T> {
    chan: Rc<RefCell<Channel<T>>>,
}

impl<T> std::fmt::Debug for Receiver<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.chan.try_borrow() {
            Ok(c) => f
                .debug_struct("Receiver")
                .field("channel", &c.id)
                .finish_non_exhaustive(),
            Err(_) => f.debug_struct("Receiver").finish_non_exhaustive(),
        }
    }
}

impl<T: Debug> Receiver<T> {
    /// Removes and returns the message at the front of the channel, blocking while it is empty.
    ///
    /// Awaiting this is a scheduling point: the recv commits (and the head is popped) only once the
    /// queue is non-empty.
    pub async fn recv(&self) -> T {
        // The `registered` guard registers at most once, so a spurious re-poll before the commit
        // re-yields instead of registering a second recv.
        let slot = Rc::new(Cell::new(None));
        let mut registered = false;
        poll_fn(move |cx| {
            if let Some(value) = slot.take() {
                return Poll::Ready(value);
            }
            if !registered {
                registered = true;
                self.chan.borrow_mut().register(
                    Op::Recv {
                        slot: Rc::clone(&slot),
                    },
                    cx.waker().clone(),
                );
            }
            Poll::Pending
        })
        .await
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::{Executor, ProcessResult};
    use std::future::Future;

    // A fresh channel with id 0, driver plus its two halves.
    fn make() -> (ChannelHandle<i32>, Sender<i32>, Receiver<i32>) {
        let driver = ChannelHandle::new(0);
        let (tx, rx) = driver.split();
        (driver, tx, rx)
    }

    // Drives the strategy by hand: keep committing the first enabled transition and resuming the
    // executor until every process finishes. The driver handle is any clone of the shared channel.
    fn drive(exec: &mut Executor, obj: &mut impl Object) {
        exec.execute().unwrap();
        while let Some(&t) = obj.enabled().first() {
            obj.apply(t);
            exec.execute().unwrap();
        }
    }

    // Runs `body` as the only process against a fresh channel.
    fn run_single(
        body: impl FnOnce(Sender<i32>, Receiver<i32>) -> Box<dyn Future<Output = ProcessResult>>,
    ) {
        let (mut driver, tx, rx) = make();
        let mut exec = Executor::default();
        exec.schedule(Box::into_pin(body(tx, rx)));
        drive(&mut exec, &mut driver);
    }

    // The first enabled op of process `pid` on the channel.
    fn enabled_of(handle: &ChannelHandle<i32>, pid: usize) -> Transition {
        *handle.enabled().iter().find(|t| t.pid == pid).unwrap()
    }

    #[test]
    fn send_then_recv_returns_value() {
        let seen = Rc::new(Cell::new(0));
        let dst = seen.clone();
        run_single(move |tx, rx| {
            Box::new(async move {
                tx.send(7).await;
                dst.set(rx.recv().await);
                Ok(())
            })
        });
        assert_eq!(seen.get(), 7);
    }

    #[test]
    fn fifo_order_for_one_producer() {
        let first = Rc::new(Cell::new(0));
        let second = Rc::new(Cell::new(0));
        let (a, b) = (first.clone(), second.clone());
        run_single(move |tx, rx| {
            Box::new(async move {
                tx.send(1).await;
                tx.send(2).await;
                a.set(rx.recv().await);
                b.set(rx.recv().await);
                Ok(())
            })
        });
        assert_eq!(first.get(), 1);
        assert_eq!(second.get(), 2);
    }

    // Sharing one Receiver across two processes (via Rc, since recv takes &self) is a
    // second consumer — rejected at registration to keep the (Recv, Recv) independence
    // sound, which would otherwise hide reachable interleavings.
    #[test]
    #[should_panic(expected = "single consumer")]
    fn second_consumer_panics() {
        let (_driver, _tx, rx) = make();
        let rx = Rc::new(rx);
        let mut exec = Executor::default();
        let r1 = rx.clone();
        exec.schedule(async move {
            r1.recv().await;
            Ok(())
        });
        let r2 = rx.clone();
        exec.schedule(async move {
            r2.recv().await;
            Ok(())
        });
        let _ = exec.execute(); // both recvs register; the second consumer panics
    }

    // A recv against an empty channel is withheld from `enabled` — that is the blocking mechanism.
    #[test]
    fn recv_on_empty_blocks() {
        let (driver, _tx, rx) = make();
        let mut exec = Executor::default();
        exec.schedule(async move {
            rx.recv().await;
            Ok(())
        });
        exec.execute().unwrap();
        assert!(
            driver.enabled().is_empty(),
            "recv must block on an empty queue"
        );
    }

    // The dependency truth table on a hand-driven executor: two sends from distinct producers and
    // one consumer. send/send is dependent; the recv depends only on the send it actually consumes.
    #[test]
    fn dependency_truth_table() {
        let (mut driver, tx, rx) = make();
        let tx2 = tx.clone();
        let mut exec = Executor::default();
        exec.schedule(async move {
            tx.send(10).await;
            Ok(())
        });
        exec.schedule(async move {
            tx2.send(20).await;
            Ok(())
        });
        exec.schedule(async move {
            rx.recv().await;
            Ok(())
        });
        exec.execute().unwrap();

        // Two registered sends and a blocked recv: only the sends are enabled.
        let send0 = enabled_of(&driver, 0);
        let send1 = enabled_of(&driver, 1);
        assert_eq!(driver.enabled().len(), 2);
        assert!(driver.depends(send0, send1), "send/send is dependent");

        // Commit send0, then the recv consuming it; the recv depends on send0 but not on send1.
        driver.apply(send0);
        exec.execute().unwrap();
        let recv = enabled_of(&driver, 2);
        driver.apply(recv);
        exec.execute().unwrap();

        assert!(
            driver.depends(send0, recv),
            "recv depends on the send it consumed"
        );
        assert!(
            !driver.depends(send1, recv),
            "recv is independent of the unconsumed send"
        );
        assert!(
            !driver.depends(recv, send1),
            "symmetric: unconsumed send is independent"
        );
    }
}