Skip to main content

Ring

Struct Ring 

Source
pub struct Ring { /* private fields */ }
Expand description

Sparse directed message rings packed into collective RMA windows.

Safe to share between threads on the same process. Lifetime is the underlying MPI window To drop the ring use Self::close (collective) or let the destructor run symmetrically on every rank.

Implementations§

Source§

impl Ring

Source

pub fn safe<C: Communicator + ?Sized>( comm: &C, rings: &[(Rank, Rank, usize, usize)], ) -> Result<Self, Error>

Construct a safe (overwrite-gated) ring. Collective over comm.

rings names the active directed lanes, one (source, destination, depth, capacity) tuple per lane: depth is the number of fixed slots the lane holds, capacity the payload bytes one slot fits. Every rank passes the same list.

Each source lane gets a cumulative-acknowledgement counter at the source The senders spin on yield_now until the receiver has acked enough earlier messages to make room in the slot ring. Requires a unified window memory model otherwise contruction fails

§Errors
Examples found in repository?
examples/test_layout.rs (line 23)
20fn run(world: &SimpleCommunicator, safe: bool) {
21    let rank = world.rank();
22    let ring = if safe {
23        Ring::safe(world, &lanes(2, 8)).unwrap()
24    } else {
25        Ring::raw(world, &lanes(2, 8)).unwrap()
26    };
27
28    if rank < 2 {
29        ring.send(2, &[rank as u8, 1]).unwrap();
30    }
31    world.barrier();
32
33    if rank == 2 {
34        let messages = ring.poll().unwrap();
35        assert_eq!(messages.len(), 2);
36        assert_eq!(messages[0].origin, 0);
37        assert_eq!(messages[0].data, vec![0, 1]);
38        assert_eq!(messages[1].origin, 1);
39        assert_eq!(messages[1].data, vec![1, 1]);
40        ring.ack(0, 1).unwrap();
41        ring.ack(1, 1).unwrap();
42        ring.send(0, &[2, 1]).unwrap();
43        ring.send(1, &[2, 1]).unwrap();
44    }
45    world.barrier();
46
47    if rank < 2 {
48        let messages = ring.poll().unwrap();
49        assert_eq!(
50            messages,
51            vec![Message {
52                origin: 2,
53                sequence: 1,
54                data: vec![2, 1]
55            }]
56        );
57        ring.ack(2, 1).unwrap();
58    }
59    world.barrier();
60
61    ring.close().unwrap();
62}
More examples
Hide additional examples
examples/test_ring.rs (line 25)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146    if rank == 0 {
147        assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148    }
149    world.barrier();
150    if rank == 1 {
151        let messages = ring.poll().unwrap();
152        assert_eq!(messages.len(), 1);
153        assert_eq!(messages[0].origin, 0);
154        assert_eq!(messages[0].data, vec![7]);
155        ring.ack(0, 1).unwrap();
156    }
157    world.barrier();
158    ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162    let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163    assert!(!ring.is_safe());
164
165    for s in 1..=3u8 {
166        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167    }
168    world.barrier();
169
170    let messages = ring.poll().unwrap();
171    assert_eq!(
172        messages,
173        vec![
174            Message {
175                origin: prev,
176                sequence: 2,
177                data: vec![prev as u8, 2]
178            },
179            Message {
180                origin: prev,
181                sequence: 3,
182                data: vec![prev as u8, 3]
183            },
184        ]
185    );
186    assert_eq!(ring.lost(), 1);
187
188    // Raw acks are no-ops.
189    ring.ack(prev, u64::MAX).unwrap();
190    world.barrier();
191    ring.close().unwrap();
192}
193
194fn main() {
195    let (universe, provided) =
196        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
197    assert_eq!(provided, Threading::Multiple);
198    let world = universe.world();
199    let rank = world.rank();
200    let size = world.size();
201    assert_eq!(size, 2);
202    let next = (rank + 1) % size;
203    let prev = (rank + size - 1) % size;
204
205    // Configuration must agree across ranks.
206    if rank == 0 {
207        assert!(matches!(
208            Ring::safe(&world, &pair(1, 8)),
209            Err(Error::Ring("configuration differs between ranks"))
210        ));
211    } else {
212        assert!(matches!(
213            Ring::safe(&world, &pair(2, 8)),
214            Err(Error::Ring("configuration differs between ranks"))
215        ));
216    }
217    assert!(matches!(
218        Ring::raw(&world, &pair(0, 8)),
219        Err(Error::Ring("depth must be positive"))
220    ));
221
222    safe_basics(&world, rank, next, prev);
223    oneway_basics(&world, rank);
224    backpressure(&world, rank);
225    raw_basics(&world, rank, next, prev);
226
227    if rank == 0 {
228        println!("test_ring: ok");
229    }
230}
examples/compare.rs (line 410)
337fn main() {
338    let mut args = std::env::args().skip(1);
339    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
340    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(32);
341    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
342    let repeats: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(5);
343
344    let (mut universe, provided) =
345        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
346    assert_eq!(provided, Threading::Multiple);
347    let world = universe.world();
348    let rank = world.rank();
349    let size = world.size();
350    assert!(size >= 2, "compare needs at least 2 ranks");
351    assert!(noise_kib == 0 || size >= 4, "noise needs at least 4 ranks");
352    // `p2p-bsend` needs somewhere to copy to. Only rank 0 sends buffered, so
353    // only it attaches the buffer; every rank allocating 512 MiB would need
354    // tens of GiB at scale. Sized past the largest amount a stream can leave
355    // in flight, so the comparison measures the transport and not a buffer
356    // running out.
357    if rank == 0 {
358        universe.set_buffer_size(BSEND_BUF);
359    }
360
361    let stop = Arc::new(AtomicBool::new(false));
362    let noisy = noise_kib > 0;
363
364    std::thread::scope(|scope| {
365        if noisy && rank >= 2 {
366            let stop = Arc::clone(&stop);
367            let shared = Shared(&world);
368            scope.spawn(move || {
369                let shared = shared;
370                noise(shared.0, noise_kib, &stop);
371            });
372        }
373        if rank > 1 {
374            // Noise ranks take no part in the measurement. They wait here for
375            // rank 0 to call time, then let their noise thread finish.
376            world
377                .process_at_rank(0)
378                .receive_vec_with_tag::<u8>(TAG_QUIT);
379            stop.store(true, Ordering::Relaxed);
380            return;
381        }
382
383        if rank == 0 {
384            println!("{HEADER}");
385        }
386
387        // The measured pair only. A world barrier here would deadlock against
388        // ranks sitting in the noise loop.
389        let pair: Vec<Rank> = vec![0, 1];
390        let group = world.group().include(&pair);
391        let duo = world
392            .split_by_subgroup(&group)
393            .expect("ranks 0 and 1 form the measured pair");
394
395        for rep in 0..repeats {
396            for payload in SIZES {
397                // Both ring modes share the lane shape, so the only difference
398                // between the two rows is the acknowledgement gate.
399                let lanes = vec![
400                    (0 as Rank, 1 as Rank, depth, payload),
401                    (1, 0, depth, payload),
402                ];
403                // Cap the stream by bytes as well as count: 256 KiB x 20k would be
404                // 5 GiB through the wire for one row.
405                let n = messages.min((STREAM_BYTES / payload as u64).max(64));
406                let iters = if payload >= 32768 { 500 } else { 2000 };
407
408                for (name, safe) in [("ring-safe", true), ("ring-raw", false)] {
409                    let ring = if safe {
410                        Ring::safe(&duo, &lanes).unwrap()
411                    } else {
412                        Ring::raw(&duo, &lanes).unwrap()
413                    };
414                    let (sent, got, inject, total, wait) =
415                        ring_stream(&duo, &ring, rank, rep, payload, n);
416                    if rank == 0 {
417                        Row {
418                            transport: name,
419                            measure: "stream",
420                            payload,
421                            sent,
422                            delivered: got,
423                            inject,
424                            total,
425                            wait,
426                            max_us: 0.0,
427                        }
428                        .print(size, depth, noise_kib, rep);
429                    }
430                    duo.barrier();
431                    let stalled = ring.wait_ns();
432                    let (dt, max_us) = ring_rtt(&ring, rank, rep, payload, iters);
433                    if rank == 0 {
434                        Row {
435                            transport: name,
436                            measure: "rtt",
437                            payload,
438                            sent: iters * 2,
439                            delivered: iters * 2,
440                            inject: dt,
441                            total: dt,
442                            wait: (ring.wait_ns() - stalled) as f64 * 1e-9,
443                            max_us,
444                        }
445                        .print(size, depth, noise_kib, rep);
446                    }
447                    duo.barrier();
448                    ring.close().unwrap();
449                }
450
451                for (name, buffered) in [("p2p-send", false), ("p2p-bsend", true)] {
452                    let (sent, got, inject, total) =
453                        p2p_stream(&duo, rank, rep, payload, n, buffered);
454                    if rank == 0 {
455                        Row {
456                            transport: name,
457                            measure: "stream",
458                            payload,
459                            sent,
460                            delivered: got,
461                            inject,
462                            total,
463                            wait: 0.0,
464                            max_us: 0.0,
465                        }
466                        .print(size, depth, noise_kib, rep);
467                    }
468                    duo.barrier();
469                }
470                let (dt, max_us) = p2p_rtt(&duo, rank, rep, payload, iters);
471                if rank == 0 {
472                    Row {
473                        transport: "p2p-send",
474                        measure: "rtt",
475                        payload,
476                        sent: iters * 2,
477                        delivered: iters * 2,
478                        inject: dt,
479                        total: dt,
480                        wait: 0.0,
481                        max_us,
482                    }
483                    .print(size, depth, noise_kib, rep);
484                }
485                duo.barrier();
486            }
487        }
488
489        if rank == 0 {
490            for r in 2..size {
491                world.process_at_rank(r).send_with_tag(&[0u8], TAG_QUIT);
492            }
493        }
494    });
495
496    // Buffered sends must be drained before MPI tears the buffer down.
497    world.barrier();
498}
examples/soak.rs (line 159)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn raw<C: Communicator + ?Sized>( comm: &C, rings: &[(Rank, Rank, usize, usize)], ) -> Result<Self, Error>

Construct a raw (overwrite-capable) ring. Collective over comm.

rings has the same shape as for Self::safe. No acknowledgement counter: unread slots are silently overwritten when the depth is exhausted, and the receiver reports the gap via Self::lost. The sender never blocks on a slow peer. Requires a unified window memory model.

§Errors

Same conditions as Self::safe.

Examples found in repository?
examples/test_ring.rs (line 162)
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162    let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163    assert!(!ring.is_safe());
164
165    for s in 1..=3u8 {
166        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167    }
168    world.barrier();
169
170    let messages = ring.poll().unwrap();
171    assert_eq!(
172        messages,
173        vec![
174            Message {
175                origin: prev,
176                sequence: 2,
177                data: vec![prev as u8, 2]
178            },
179            Message {
180                origin: prev,
181                sequence: 3,
182                data: vec![prev as u8, 3]
183            },
184        ]
185    );
186    assert_eq!(ring.lost(), 1);
187
188    // Raw acks are no-ops.
189    ring.ack(prev, u64::MAX).unwrap();
190    world.barrier();
191    ring.close().unwrap();
192}
193
194fn main() {
195    let (universe, provided) =
196        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
197    assert_eq!(provided, Threading::Multiple);
198    let world = universe.world();
199    let rank = world.rank();
200    let size = world.size();
201    assert_eq!(size, 2);
202    let next = (rank + 1) % size;
203    let prev = (rank + size - 1) % size;
204
205    // Configuration must agree across ranks.
206    if rank == 0 {
207        assert!(matches!(
208            Ring::safe(&world, &pair(1, 8)),
209            Err(Error::Ring("configuration differs between ranks"))
210        ));
211    } else {
212        assert!(matches!(
213            Ring::safe(&world, &pair(2, 8)),
214            Err(Error::Ring("configuration differs between ranks"))
215        ));
216    }
217    assert!(matches!(
218        Ring::raw(&world, &pair(0, 8)),
219        Err(Error::Ring("depth must be positive"))
220    ));
221
222    safe_basics(&world, rank, next, prev);
223    oneway_basics(&world, rank);
224    backpressure(&world, rank);
225    raw_basics(&world, rank, next, prev);
226
227    if rank == 0 {
228        println!("test_ring: ok");
229    }
230}
More examples
Hide additional examples
examples/test_layout.rs (line 25)
20fn run(world: &SimpleCommunicator, safe: bool) {
21    let rank = world.rank();
22    let ring = if safe {
23        Ring::safe(world, &lanes(2, 8)).unwrap()
24    } else {
25        Ring::raw(world, &lanes(2, 8)).unwrap()
26    };
27
28    if rank < 2 {
29        ring.send(2, &[rank as u8, 1]).unwrap();
30    }
31    world.barrier();
32
33    if rank == 2 {
34        let messages = ring.poll().unwrap();
35        assert_eq!(messages.len(), 2);
36        assert_eq!(messages[0].origin, 0);
37        assert_eq!(messages[0].data, vec![0, 1]);
38        assert_eq!(messages[1].origin, 1);
39        assert_eq!(messages[1].data, vec![1, 1]);
40        ring.ack(0, 1).unwrap();
41        ring.ack(1, 1).unwrap();
42        ring.send(0, &[2, 1]).unwrap();
43        ring.send(1, &[2, 1]).unwrap();
44    }
45    world.barrier();
46
47    if rank < 2 {
48        let messages = ring.poll().unwrap();
49        assert_eq!(
50            messages,
51            vec![Message {
52                origin: 2,
53                sequence: 1,
54                data: vec![2, 1]
55            }]
56        );
57        ring.ack(2, 1).unwrap();
58    }
59    world.barrier();
60
61    ring.close().unwrap();
62}
examples/compare.rs (line 412)
337fn main() {
338    let mut args = std::env::args().skip(1);
339    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
340    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(32);
341    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
342    let repeats: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(5);
343
344    let (mut universe, provided) =
345        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
346    assert_eq!(provided, Threading::Multiple);
347    let world = universe.world();
348    let rank = world.rank();
349    let size = world.size();
350    assert!(size >= 2, "compare needs at least 2 ranks");
351    assert!(noise_kib == 0 || size >= 4, "noise needs at least 4 ranks");
352    // `p2p-bsend` needs somewhere to copy to. Only rank 0 sends buffered, so
353    // only it attaches the buffer; every rank allocating 512 MiB would need
354    // tens of GiB at scale. Sized past the largest amount a stream can leave
355    // in flight, so the comparison measures the transport and not a buffer
356    // running out.
357    if rank == 0 {
358        universe.set_buffer_size(BSEND_BUF);
359    }
360
361    let stop = Arc::new(AtomicBool::new(false));
362    let noisy = noise_kib > 0;
363
364    std::thread::scope(|scope| {
365        if noisy && rank >= 2 {
366            let stop = Arc::clone(&stop);
367            let shared = Shared(&world);
368            scope.spawn(move || {
369                let shared = shared;
370                noise(shared.0, noise_kib, &stop);
371            });
372        }
373        if rank > 1 {
374            // Noise ranks take no part in the measurement. They wait here for
375            // rank 0 to call time, then let their noise thread finish.
376            world
377                .process_at_rank(0)
378                .receive_vec_with_tag::<u8>(TAG_QUIT);
379            stop.store(true, Ordering::Relaxed);
380            return;
381        }
382
383        if rank == 0 {
384            println!("{HEADER}");
385        }
386
387        // The measured pair only. A world barrier here would deadlock against
388        // ranks sitting in the noise loop.
389        let pair: Vec<Rank> = vec![0, 1];
390        let group = world.group().include(&pair);
391        let duo = world
392            .split_by_subgroup(&group)
393            .expect("ranks 0 and 1 form the measured pair");
394
395        for rep in 0..repeats {
396            for payload in SIZES {
397                // Both ring modes share the lane shape, so the only difference
398                // between the two rows is the acknowledgement gate.
399                let lanes = vec![
400                    (0 as Rank, 1 as Rank, depth, payload),
401                    (1, 0, depth, payload),
402                ];
403                // Cap the stream by bytes as well as count: 256 KiB x 20k would be
404                // 5 GiB through the wire for one row.
405                let n = messages.min((STREAM_BYTES / payload as u64).max(64));
406                let iters = if payload >= 32768 { 500 } else { 2000 };
407
408                for (name, safe) in [("ring-safe", true), ("ring-raw", false)] {
409                    let ring = if safe {
410                        Ring::safe(&duo, &lanes).unwrap()
411                    } else {
412                        Ring::raw(&duo, &lanes).unwrap()
413                    };
414                    let (sent, got, inject, total, wait) =
415                        ring_stream(&duo, &ring, rank, rep, payload, n);
416                    if rank == 0 {
417                        Row {
418                            transport: name,
419                            measure: "stream",
420                            payload,
421                            sent,
422                            delivered: got,
423                            inject,
424                            total,
425                            wait,
426                            max_us: 0.0,
427                        }
428                        .print(size, depth, noise_kib, rep);
429                    }
430                    duo.barrier();
431                    let stalled = ring.wait_ns();
432                    let (dt, max_us) = ring_rtt(&ring, rank, rep, payload, iters);
433                    if rank == 0 {
434                        Row {
435                            transport: name,
436                            measure: "rtt",
437                            payload,
438                            sent: iters * 2,
439                            delivered: iters * 2,
440                            inject: dt,
441                            total: dt,
442                            wait: (ring.wait_ns() - stalled) as f64 * 1e-9,
443                            max_us,
444                        }
445                        .print(size, depth, noise_kib, rep);
446                    }
447                    duo.barrier();
448                    ring.close().unwrap();
449                }
450
451                for (name, buffered) in [("p2p-send", false), ("p2p-bsend", true)] {
452                    let (sent, got, inject, total) =
453                        p2p_stream(&duo, rank, rep, payload, n, buffered);
454                    if rank == 0 {
455                        Row {
456                            transport: name,
457                            measure: "stream",
458                            payload,
459                            sent,
460                            delivered: got,
461                            inject,
462                            total,
463                            wait: 0.0,
464                            max_us: 0.0,
465                        }
466                        .print(size, depth, noise_kib, rep);
467                    }
468                    duo.barrier();
469                }
470                let (dt, max_us) = p2p_rtt(&duo, rank, rep, payload, iters);
471                if rank == 0 {
472                    Row {
473                        transport: "p2p-send",
474                        measure: "rtt",
475                        payload,
476                        sent: iters * 2,
477                        delivered: iters * 2,
478                        inject: dt,
479                        total: dt,
480                        wait: 0.0,
481                        max_us,
482                    }
483                    .print(size, depth, noise_kib, rep);
484                }
485                duo.barrier();
486            }
487        }
488
489        if rank == 0 {
490            for r in 2..size {
491                world.process_at_rank(r).send_with_tag(&[0u8], TAG_QUIT);
492            }
493        }
494    });
495
496    // Buffered sends must be drained before MPI tears the buffer down.
497    world.barrier();
498}
examples/soak.rs (line 161)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn is_safe(&self) -> bool

Whether this ring gates overwrites with cumulative acknowledgements.

Examples found in repository?
examples/test_ring.rs (line 26)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146    if rank == 0 {
147        assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148    }
149    world.barrier();
150    if rank == 1 {
151        let messages = ring.poll().unwrap();
152        assert_eq!(messages.len(), 1);
153        assert_eq!(messages[0].origin, 0);
154        assert_eq!(messages[0].data, vec![7]);
155        ring.ack(0, 1).unwrap();
156    }
157    world.barrier();
158    ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162    let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163    assert!(!ring.is_safe());
164
165    for s in 1..=3u8 {
166        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167    }
168    world.barrier();
169
170    let messages = ring.poll().unwrap();
171    assert_eq!(
172        messages,
173        vec![
174            Message {
175                origin: prev,
176                sequence: 2,
177                data: vec![prev as u8, 2]
178            },
179            Message {
180                origin: prev,
181                sequence: 3,
182                data: vec![prev as u8, 3]
183            },
184        ]
185    );
186    assert_eq!(ring.lost(), 1);
187
188    // Raw acks are no-ops.
189    ring.ack(prev, u64::MAX).unwrap();
190    world.barrier();
191    ring.close().unwrap();
192}
Source

pub fn depth(&self, destination: Rank) -> Option<usize>

Slot count configured for the outgoing lane to destination, if any.

Examples found in repository?
examples/test_ring.rs (line 27)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
Source

pub fn capacity(&self, destination: Rank) -> Option<usize>

Per-slot payload capacity configured for the outgoing lane to destination, if any.

Examples found in repository?
examples/test_ring.rs (line 28)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
Source

pub fn lost(&self) -> u64

Total messages observed as lost (raw mode only).

Examples found in repository?
examples/test_ring.rs (line 63)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146    if rank == 0 {
147        assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148    }
149    world.barrier();
150    if rank == 1 {
151        let messages = ring.poll().unwrap();
152        assert_eq!(messages.len(), 1);
153        assert_eq!(messages[0].origin, 0);
154        assert_eq!(messages[0].data, vec![7]);
155        ring.ack(0, 1).unwrap();
156    }
157    world.barrier();
158    ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162    let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163    assert!(!ring.is_safe());
164
165    for s in 1..=3u8 {
166        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167    }
168    world.barrier();
169
170    let messages = ring.poll().unwrap();
171    assert_eq!(
172        messages,
173        vec![
174            Message {
175                origin: prev,
176                sequence: 2,
177                data: vec![prev as u8, 2]
178            },
179            Message {
180                origin: prev,
181                sequence: 3,
182                data: vec![prev as u8, 3]
183            },
184        ]
185    );
186    assert_eq!(ring.lost(), 1);
187
188    // Raw acks are no-ops.
189    ring.ack(prev, u64::MAX).unwrap();
190    world.barrier();
191    ring.close().unwrap();
192}
More examples
Hide additional examples
examples/soak.rs (line 271)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn corrupt(&self) -> u64

Total corrupt slot reads: torn header/footer, bad length, or CRC mismatch.

Examples found in repository?
examples/soak.rs (line 272)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn max_lag(&self) -> u64

Greatest sequence advance one Self::poll made on one lane.

In raw mode this includes gaps counted as lost. In safe mode it is the number of messages drained.

Examples found in repository?
examples/test_ring.rs (line 64)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
More examples
Hide additional examples
examples/soak.rs (line 298)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn waits(&self) -> u64

Number of times a safe-mode sender actually blocked on acknowledgements.

Refreshing the cached counter does not count; only a sender that found no headroom after refreshing and had to spin does.

Examples found in repository?
examples/test_ring.rs (line 103)
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
More examples
Hide additional examples
examples/soak.rs (line 299)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn wait_ns(&self) -> u64

Cumulative nanoseconds spent in safe-mode wait spins.

Examples found in repository?
examples/test_ring.rs (line 113)
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
More examples
Hide additional examples
examples/compare.rs (line 146)
137fn ring_stream(
138    comm: &SimpleCommunicator,
139    ring: &Ring,
140    rank: Rank,
141    rep: u32,
142    payload: usize,
143    n: u64,
144) -> (u64, u64, f64, f64, f64) {
145    comm.barrier();
146    let stalled = ring.wait_ns();
147    let t0 = Instant::now();
148    if rank == 0 {
149        let buf = shuffled(rep, payload);
150        let mut sent = 0u64;
151        while sent < n {
152            ring.send(1, &buf).unwrap();
153            sent += 1;
154        }
155        let inject = t0.elapsed().as_secs_f64();
156        let wait = (ring.wait_ns() - stalled) as f64 * 1e-9;
157        // Tell the drain side to stop, then collect what it actually saw.
158        comm.process_at_rank(1).send_with_tag(&[0u8], TAG_STOP);
159        let (reply, _) = comm
160            .process_at_rank(1)
161            .receive_vec_with_tag::<u64>(TAG_STOP);
162        (sent, reply[0], inject, t0.elapsed().as_secs_f64(), wait)
163    } else {
164        let mut got = 0u64;
165        let mut stopping = false;
166        loop {
167            let batch = ring.poll().unwrap();
168            let empty = batch.is_empty();
169            let mut furthest = 0u64;
170            for m in &batch {
171                got += 1;
172                furthest = furthest.max(m.sequence);
173            }
174            if furthest > 0 {
175                ring.ack(0, furthest).unwrap();
176            }
177            if stopping && empty {
178                break;
179            }
180            if !stopping
181                && comm
182                    .any_process()
183                    .immediate_probe_with_tag(TAG_STOP)
184                    .is_some()
185            {
186                comm.any_process().receive_vec_with_tag::<u8>(TAG_STOP);
187                stopping = true;
188            }
189            if empty {
190                std::thread::yield_now();
191            }
192        }
193        comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
194        let dt = t0.elapsed().as_secs_f64();
195        (0, got, dt, dt, 0.0)
196    }
197}
198
199/// One message each way, `iters` times, on the ring.
200///
201/// Returns `(total, max_us)`: the run wall time and the worst single
202/// round trip, halved to a one-way latency so it shares the `us_per_msg`
203/// unit of the mean.
204fn ring_rtt(ring: &Ring, rank: Rank, rep: u32, payload: usize, iters: u64) -> (f64, f64) {
205    let buf = shuffled(rep, payload);
206    let other: Rank = if rank == 0 { 1 } else { 0 };
207    let mut seen = 0u64;
208    let await_one = |ring: &Ring, seen: &mut u64| {
209        loop {
210            let batch = ring.poll().unwrap();
211            if let Some(last) = batch.last() {
212                *seen = last.sequence;
213                ring.ack(other, *seen).unwrap();
214                return;
215            }
216            std::hint::spin_loop();
217        }
218    };
219    let mut max_us = 0.0f64;
220    let t0 = Instant::now();
221    for _ in 0..iters {
222        let start = Instant::now();
223        if rank == 0 {
224            ring.send(1, &buf).unwrap();
225            await_one(ring, &mut seen);
226        } else {
227            await_one(ring, &mut seen);
228            ring.send(0, &buf).unwrap();
229        }
230        max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
231    }
232    (t0.elapsed().as_secs_f64(), max_us)
233}
234
235// ── Point to point ──────────────────────────────────────────────────────────
236
237fn p2p_stream(
238    comm: &SimpleCommunicator,
239    rank: Rank,
240    rep: u32,
241    payload: usize,
242    n: u64,
243    buffered: bool,
244) -> (u64, u64, f64, f64) {
245    comm.barrier();
246    let t0 = Instant::now();
247    if rank == 0 {
248        let buf = shuffled(rep, payload);
249        let peer = comm.process_at_rank(1);
250        let mut sent = 0u64;
251        while sent < n {
252            if buffered {
253                peer.buffered_send_with_tag(&buf[..], TAG_DATA);
254            } else {
255                peer.send_with_tag(&buf[..], TAG_DATA);
256            }
257            sent += 1;
258        }
259        let inject = t0.elapsed().as_secs_f64();
260        peer.send_with_tag(&[0u8], TAG_STOP);
261        // The receiver's count is authoritative and equals the sender's here:
262        // p2p does not drop. Waiting for it also drains the queue before the
263        // next measurement starts.
264        let (reply, _) = comm
265            .process_at_rank(1)
266            .receive_vec_with_tag::<u64>(TAG_STOP);
267        (sent, reply[0], inject, t0.elapsed().as_secs_f64())
268    } else {
269        let mut got = 0u64;
270        let source = comm.process_at_rank(0);
271        loop {
272            let (_, status) = source.receive_vec::<u8>();
273            if status.tag() == TAG_STOP {
274                break;
275            }
276            got += 1;
277        }
278        comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
279        let dt = t0.elapsed().as_secs_f64();
280        (got, got, dt, dt)
281    }
282}
283
284fn p2p_rtt(
285    comm: &SimpleCommunicator,
286    rank: Rank,
287    rep: u32,
288    payload: usize,
289    iters: u64,
290) -> (f64, f64) {
291    let buf = shuffled(rep, payload);
292    let peer = comm.process_at_rank(if rank == 0 { 1 } else { 0 });
293    let mut max_us = 0.0f64;
294    let t0 = Instant::now();
295    for _ in 0..iters {
296        let start = Instant::now();
297        if rank == 0 {
298            peer.send_with_tag(&buf[..], TAG_DATA);
299            peer.receive_vec_with_tag::<u8>(TAG_DATA);
300        } else {
301            peer.receive_vec_with_tag::<u8>(TAG_DATA);
302            peer.send_with_tag(&buf[..], TAG_DATA);
303        }
304        max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
305    }
306    (t0.elapsed().as_secs_f64(), max_us)
307}
308
309/// Background ping-pong among ranks 2+, in pairs, until `stop`.
310fn noise(world: &SimpleCommunicator, kib: usize, stop: &AtomicBool) {
311    let rank = world.rank();
312    let base = rank - 2;
313    let partner = if base % 2 == 0 { rank + 1 } else { rank - 1 };
314    if partner >= world.size() {
315        return;
316    }
317    let buf = vec![0xA5u8; kib * 1024];
318    let peer = world.process_at_rank(partner);
319    loop {
320        if rank < partner {
321            if stop.load(Ordering::Relaxed) {
322                peer.send_with_tag(&[0u8], TAG_QUIT);
323                return;
324            }
325            peer.send_with_tag(&buf[..], TAG_NOISE);
326            peer.receive_vec_with_tag::<u8>(TAG_NOISE);
327        } else {
328            let (_, status) = peer.receive_vec::<u8>();
329            if status.tag() == TAG_QUIT {
330                return;
331            }
332            peer.send_with_tag(&buf[..], TAG_NOISE);
333        }
334    }
335}
336
337fn main() {
338    let mut args = std::env::args().skip(1);
339    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
340    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(32);
341    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
342    let repeats: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(5);
343
344    let (mut universe, provided) =
345        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
346    assert_eq!(provided, Threading::Multiple);
347    let world = universe.world();
348    let rank = world.rank();
349    let size = world.size();
350    assert!(size >= 2, "compare needs at least 2 ranks");
351    assert!(noise_kib == 0 || size >= 4, "noise needs at least 4 ranks");
352    // `p2p-bsend` needs somewhere to copy to. Only rank 0 sends buffered, so
353    // only it attaches the buffer; every rank allocating 512 MiB would need
354    // tens of GiB at scale. Sized past the largest amount a stream can leave
355    // in flight, so the comparison measures the transport and not a buffer
356    // running out.
357    if rank == 0 {
358        universe.set_buffer_size(BSEND_BUF);
359    }
360
361    let stop = Arc::new(AtomicBool::new(false));
362    let noisy = noise_kib > 0;
363
364    std::thread::scope(|scope| {
365        if noisy && rank >= 2 {
366            let stop = Arc::clone(&stop);
367            let shared = Shared(&world);
368            scope.spawn(move || {
369                let shared = shared;
370                noise(shared.0, noise_kib, &stop);
371            });
372        }
373        if rank > 1 {
374            // Noise ranks take no part in the measurement. They wait here for
375            // rank 0 to call time, then let their noise thread finish.
376            world
377                .process_at_rank(0)
378                .receive_vec_with_tag::<u8>(TAG_QUIT);
379            stop.store(true, Ordering::Relaxed);
380            return;
381        }
382
383        if rank == 0 {
384            println!("{HEADER}");
385        }
386
387        // The measured pair only. A world barrier here would deadlock against
388        // ranks sitting in the noise loop.
389        let pair: Vec<Rank> = vec![0, 1];
390        let group = world.group().include(&pair);
391        let duo = world
392            .split_by_subgroup(&group)
393            .expect("ranks 0 and 1 form the measured pair");
394
395        for rep in 0..repeats {
396            for payload in SIZES {
397                // Both ring modes share the lane shape, so the only difference
398                // between the two rows is the acknowledgement gate.
399                let lanes = vec![
400                    (0 as Rank, 1 as Rank, depth, payload),
401                    (1, 0, depth, payload),
402                ];
403                // Cap the stream by bytes as well as count: 256 KiB x 20k would be
404                // 5 GiB through the wire for one row.
405                let n = messages.min((STREAM_BYTES / payload as u64).max(64));
406                let iters = if payload >= 32768 { 500 } else { 2000 };
407
408                for (name, safe) in [("ring-safe", true), ("ring-raw", false)] {
409                    let ring = if safe {
410                        Ring::safe(&duo, &lanes).unwrap()
411                    } else {
412                        Ring::raw(&duo, &lanes).unwrap()
413                    };
414                    let (sent, got, inject, total, wait) =
415                        ring_stream(&duo, &ring, rank, rep, payload, n);
416                    if rank == 0 {
417                        Row {
418                            transport: name,
419                            measure: "stream",
420                            payload,
421                            sent,
422                            delivered: got,
423                            inject,
424                            total,
425                            wait,
426                            max_us: 0.0,
427                        }
428                        .print(size, depth, noise_kib, rep);
429                    }
430                    duo.barrier();
431                    let stalled = ring.wait_ns();
432                    let (dt, max_us) = ring_rtt(&ring, rank, rep, payload, iters);
433                    if rank == 0 {
434                        Row {
435                            transport: name,
436                            measure: "rtt",
437                            payload,
438                            sent: iters * 2,
439                            delivered: iters * 2,
440                            inject: dt,
441                            total: dt,
442                            wait: (ring.wait_ns() - stalled) as f64 * 1e-9,
443                            max_us,
444                        }
445                        .print(size, depth, noise_kib, rep);
446                    }
447                    duo.barrier();
448                    ring.close().unwrap();
449                }
450
451                for (name, buffered) in [("p2p-send", false), ("p2p-bsend", true)] {
452                    let (sent, got, inject, total) =
453                        p2p_stream(&duo, rank, rep, payload, n, buffered);
454                    if rank == 0 {
455                        Row {
456                            transport: name,
457                            measure: "stream",
458                            payload,
459                            sent,
460                            delivered: got,
461                            inject,
462                            total,
463                            wait: 0.0,
464                            max_us: 0.0,
465                        }
466                        .print(size, depth, noise_kib, rep);
467                    }
468                    duo.barrier();
469                }
470                let (dt, max_us) = p2p_rtt(&duo, rank, rep, payload, iters);
471                if rank == 0 {
472                    Row {
473                        transport: "p2p-send",
474                        measure: "rtt",
475                        payload,
476                        sent: iters * 2,
477                        delivered: iters * 2,
478                        inject: dt,
479                        total: dt,
480                        wait: 0.0,
481                        max_us,
482                    }
483                    .print(size, depth, noise_kib, rep);
484                }
485                duo.barrier();
486            }
487        }
488
489        if rank == 0 {
490            for r in 2..size {
491                world.process_at_rank(r).send_with_tag(&[0u8], TAG_QUIT);
492            }
493        }
494    });
495
496    // Buffered sends must be drained before MPI tears the buffer down.
497    world.barrier();
498}
examples/soak.rs (line 300)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn send(&self, destination: Rank, data: &[u8]) -> Result<u64, Error>

Send one message and return its sequence number.

Completes the put at the target before returning. In safe mode, spins on yield_now if the destination has not yet acknowledged enough earlier messages to make room in the slot ring; the wait is recorded in Self::waits and Self::wait_ns.

§Errors
  • Error::Rank if destination is outside the communicator.
  • Error::Ring if no lane to destination is configured, the sequence number would overflow, the acknowledgement counter regresses or exceeds what was sent, or the send state is poisoned.
  • Error::Payload if data does not fit the lane’s capacity.
  • Whatever the underlying MPI_Put fails with (wrapped), e.g. Error::Mpi or Error::Range.
Examples found in repository?
examples/test_layout.rs (line 29)
20fn run(world: &SimpleCommunicator, safe: bool) {
21    let rank = world.rank();
22    let ring = if safe {
23        Ring::safe(world, &lanes(2, 8)).unwrap()
24    } else {
25        Ring::raw(world, &lanes(2, 8)).unwrap()
26    };
27
28    if rank < 2 {
29        ring.send(2, &[rank as u8, 1]).unwrap();
30    }
31    world.barrier();
32
33    if rank == 2 {
34        let messages = ring.poll().unwrap();
35        assert_eq!(messages.len(), 2);
36        assert_eq!(messages[0].origin, 0);
37        assert_eq!(messages[0].data, vec![0, 1]);
38        assert_eq!(messages[1].origin, 1);
39        assert_eq!(messages[1].data, vec![1, 1]);
40        ring.ack(0, 1).unwrap();
41        ring.ack(1, 1).unwrap();
42        ring.send(0, &[2, 1]).unwrap();
43        ring.send(1, &[2, 1]).unwrap();
44    }
45    world.barrier();
46
47    if rank < 2 {
48        let messages = ring.poll().unwrap();
49        assert_eq!(
50            messages,
51            vec![Message {
52                origin: 2,
53                sequence: 1,
54                data: vec![2, 1]
55            }]
56        );
57        ring.ack(2, 1).unwrap();
58    }
59    world.barrier();
60
61    ring.close().unwrap();
62}
More examples
Hide additional examples
examples/test_ring.rs (line 31)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146    if rank == 0 {
147        assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148    }
149    world.barrier();
150    if rank == 1 {
151        let messages = ring.poll().unwrap();
152        assert_eq!(messages.len(), 1);
153        assert_eq!(messages[0].origin, 0);
154        assert_eq!(messages[0].data, vec![7]);
155        ring.ack(0, 1).unwrap();
156    }
157    world.barrier();
158    ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162    let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163    assert!(!ring.is_safe());
164
165    for s in 1..=3u8 {
166        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167    }
168    world.barrier();
169
170    let messages = ring.poll().unwrap();
171    assert_eq!(
172        messages,
173        vec![
174            Message {
175                origin: prev,
176                sequence: 2,
177                data: vec![prev as u8, 2]
178            },
179            Message {
180                origin: prev,
181                sequence: 3,
182                data: vec![prev as u8, 3]
183            },
184        ]
185    );
186    assert_eq!(ring.lost(), 1);
187
188    // Raw acks are no-ops.
189    ring.ack(prev, u64::MAX).unwrap();
190    world.barrier();
191    ring.close().unwrap();
192}
examples/compare.rs (line 152)
137fn ring_stream(
138    comm: &SimpleCommunicator,
139    ring: &Ring,
140    rank: Rank,
141    rep: u32,
142    payload: usize,
143    n: u64,
144) -> (u64, u64, f64, f64, f64) {
145    comm.barrier();
146    let stalled = ring.wait_ns();
147    let t0 = Instant::now();
148    if rank == 0 {
149        let buf = shuffled(rep, payload);
150        let mut sent = 0u64;
151        while sent < n {
152            ring.send(1, &buf).unwrap();
153            sent += 1;
154        }
155        let inject = t0.elapsed().as_secs_f64();
156        let wait = (ring.wait_ns() - stalled) as f64 * 1e-9;
157        // Tell the drain side to stop, then collect what it actually saw.
158        comm.process_at_rank(1).send_with_tag(&[0u8], TAG_STOP);
159        let (reply, _) = comm
160            .process_at_rank(1)
161            .receive_vec_with_tag::<u64>(TAG_STOP);
162        (sent, reply[0], inject, t0.elapsed().as_secs_f64(), wait)
163    } else {
164        let mut got = 0u64;
165        let mut stopping = false;
166        loop {
167            let batch = ring.poll().unwrap();
168            let empty = batch.is_empty();
169            let mut furthest = 0u64;
170            for m in &batch {
171                got += 1;
172                furthest = furthest.max(m.sequence);
173            }
174            if furthest > 0 {
175                ring.ack(0, furthest).unwrap();
176            }
177            if stopping && empty {
178                break;
179            }
180            if !stopping
181                && comm
182                    .any_process()
183                    .immediate_probe_with_tag(TAG_STOP)
184                    .is_some()
185            {
186                comm.any_process().receive_vec_with_tag::<u8>(TAG_STOP);
187                stopping = true;
188            }
189            if empty {
190                std::thread::yield_now();
191            }
192        }
193        comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
194        let dt = t0.elapsed().as_secs_f64();
195        (0, got, dt, dt, 0.0)
196    }
197}
198
199/// One message each way, `iters` times, on the ring.
200///
201/// Returns `(total, max_us)`: the run wall time and the worst single
202/// round trip, halved to a one-way latency so it shares the `us_per_msg`
203/// unit of the mean.
204fn ring_rtt(ring: &Ring, rank: Rank, rep: u32, payload: usize, iters: u64) -> (f64, f64) {
205    let buf = shuffled(rep, payload);
206    let other: Rank = if rank == 0 { 1 } else { 0 };
207    let mut seen = 0u64;
208    let await_one = |ring: &Ring, seen: &mut u64| {
209        loop {
210            let batch = ring.poll().unwrap();
211            if let Some(last) = batch.last() {
212                *seen = last.sequence;
213                ring.ack(other, *seen).unwrap();
214                return;
215            }
216            std::hint::spin_loop();
217        }
218    };
219    let mut max_us = 0.0f64;
220    let t0 = Instant::now();
221    for _ in 0..iters {
222        let start = Instant::now();
223        if rank == 0 {
224            ring.send(1, &buf).unwrap();
225            await_one(ring, &mut seen);
226        } else {
227            await_one(ring, &mut seen);
228            ring.send(0, &buf).unwrap();
229        }
230        max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
231    }
232    (t0.elapsed().as_secs_f64(), max_us)
233}
examples/soak.rs (line 241)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn poll(&self) -> Result<Vec<Message>, Error>

Drain every incoming lane and return newly observed messages.

Does not call MPI. Safe mode delivers in-order; raw mode scans all slots when one is lapped, sorts the survivors, and counts the gaps into Self::lost. The per-origin seen cursor only advances.

§Errors
  • Error::Lapped if a safe lane was overwritten before consumption, which the acknowledge gate makes impossible and so indicates a corrupted counter or window rather than backpressure.
  • Any window read error from the local slot memory.
Examples found in repository?
examples/test_layout.rs (line 34)
20fn run(world: &SimpleCommunicator, safe: bool) {
21    let rank = world.rank();
22    let ring = if safe {
23        Ring::safe(world, &lanes(2, 8)).unwrap()
24    } else {
25        Ring::raw(world, &lanes(2, 8)).unwrap()
26    };
27
28    if rank < 2 {
29        ring.send(2, &[rank as u8, 1]).unwrap();
30    }
31    world.barrier();
32
33    if rank == 2 {
34        let messages = ring.poll().unwrap();
35        assert_eq!(messages.len(), 2);
36        assert_eq!(messages[0].origin, 0);
37        assert_eq!(messages[0].data, vec![0, 1]);
38        assert_eq!(messages[1].origin, 1);
39        assert_eq!(messages[1].data, vec![1, 1]);
40        ring.ack(0, 1).unwrap();
41        ring.ack(1, 1).unwrap();
42        ring.send(0, &[2, 1]).unwrap();
43        ring.send(1, &[2, 1]).unwrap();
44    }
45    world.barrier();
46
47    if rank < 2 {
48        let messages = ring.poll().unwrap();
49        assert_eq!(
50            messages,
51            vec![Message {
52                origin: 2,
53                sequence: 1,
54                data: vec![2, 1]
55            }]
56        );
57        ring.ack(2, 1).unwrap();
58    }
59    world.barrier();
60
61    ring.close().unwrap();
62}
More examples
Hide additional examples
examples/test_ring.rs (line 51)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146    if rank == 0 {
147        assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148    }
149    world.barrier();
150    if rank == 1 {
151        let messages = ring.poll().unwrap();
152        assert_eq!(messages.len(), 1);
153        assert_eq!(messages[0].origin, 0);
154        assert_eq!(messages[0].data, vec![7]);
155        ring.ack(0, 1).unwrap();
156    }
157    world.barrier();
158    ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162    let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163    assert!(!ring.is_safe());
164
165    for s in 1..=3u8 {
166        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167    }
168    world.barrier();
169
170    let messages = ring.poll().unwrap();
171    assert_eq!(
172        messages,
173        vec![
174            Message {
175                origin: prev,
176                sequence: 2,
177                data: vec![prev as u8, 2]
178            },
179            Message {
180                origin: prev,
181                sequence: 3,
182                data: vec![prev as u8, 3]
183            },
184        ]
185    );
186    assert_eq!(ring.lost(), 1);
187
188    // Raw acks are no-ops.
189    ring.ack(prev, u64::MAX).unwrap();
190    world.barrier();
191    ring.close().unwrap();
192}
examples/compare.rs (line 167)
137fn ring_stream(
138    comm: &SimpleCommunicator,
139    ring: &Ring,
140    rank: Rank,
141    rep: u32,
142    payload: usize,
143    n: u64,
144) -> (u64, u64, f64, f64, f64) {
145    comm.barrier();
146    let stalled = ring.wait_ns();
147    let t0 = Instant::now();
148    if rank == 0 {
149        let buf = shuffled(rep, payload);
150        let mut sent = 0u64;
151        while sent < n {
152            ring.send(1, &buf).unwrap();
153            sent += 1;
154        }
155        let inject = t0.elapsed().as_secs_f64();
156        let wait = (ring.wait_ns() - stalled) as f64 * 1e-9;
157        // Tell the drain side to stop, then collect what it actually saw.
158        comm.process_at_rank(1).send_with_tag(&[0u8], TAG_STOP);
159        let (reply, _) = comm
160            .process_at_rank(1)
161            .receive_vec_with_tag::<u64>(TAG_STOP);
162        (sent, reply[0], inject, t0.elapsed().as_secs_f64(), wait)
163    } else {
164        let mut got = 0u64;
165        let mut stopping = false;
166        loop {
167            let batch = ring.poll().unwrap();
168            let empty = batch.is_empty();
169            let mut furthest = 0u64;
170            for m in &batch {
171                got += 1;
172                furthest = furthest.max(m.sequence);
173            }
174            if furthest > 0 {
175                ring.ack(0, furthest).unwrap();
176            }
177            if stopping && empty {
178                break;
179            }
180            if !stopping
181                && comm
182                    .any_process()
183                    .immediate_probe_with_tag(TAG_STOP)
184                    .is_some()
185            {
186                comm.any_process().receive_vec_with_tag::<u8>(TAG_STOP);
187                stopping = true;
188            }
189            if empty {
190                std::thread::yield_now();
191            }
192        }
193        comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
194        let dt = t0.elapsed().as_secs_f64();
195        (0, got, dt, dt, 0.0)
196    }
197}
198
199/// One message each way, `iters` times, on the ring.
200///
201/// Returns `(total, max_us)`: the run wall time and the worst single
202/// round trip, halved to a one-way latency so it shares the `us_per_msg`
203/// unit of the mean.
204fn ring_rtt(ring: &Ring, rank: Rank, rep: u32, payload: usize, iters: u64) -> (f64, f64) {
205    let buf = shuffled(rep, payload);
206    let other: Rank = if rank == 0 { 1 } else { 0 };
207    let mut seen = 0u64;
208    let await_one = |ring: &Ring, seen: &mut u64| {
209        loop {
210            let batch = ring.poll().unwrap();
211            if let Some(last) = batch.last() {
212                *seen = last.sequence;
213                ring.ack(other, *seen).unwrap();
214                return;
215            }
216            std::hint::spin_loop();
217        }
218    };
219    let mut max_us = 0.0f64;
220    let t0 = Instant::now();
221    for _ in 0..iters {
222        let start = Instant::now();
223        if rank == 0 {
224            ring.send(1, &buf).unwrap();
225            await_one(ring, &mut seen);
226        } else {
227            await_one(ring, &mut seen);
228            ring.send(0, &buf).unwrap();
229        }
230        max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
231    }
232    (t0.elapsed().as_secs_f64(), max_us)
233}
examples/soak.rs (line 194)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn ack(&self, origin: Rank, sequence: u64) -> Result<(), Error>

Acknowledge all messages from origin through sequence.

Cumulative and idempotent. In safe mode performs one atomic MPI_Fetch_and_op against origin’s counter window. In raw mode this is a no-op (returns Ok).

§Errors
  • Error::Rank if origin is outside the communicator.
  • Error::Ring if no incoming lane from origin is configured, or the counter diverges from the locally tracked value.
  • Error::Ack if sequence is past the last received message.
Examples found in repository?
examples/test_layout.rs (line 40)
20fn run(world: &SimpleCommunicator, safe: bool) {
21    let rank = world.rank();
22    let ring = if safe {
23        Ring::safe(world, &lanes(2, 8)).unwrap()
24    } else {
25        Ring::raw(world, &lanes(2, 8)).unwrap()
26    };
27
28    if rank < 2 {
29        ring.send(2, &[rank as u8, 1]).unwrap();
30    }
31    world.barrier();
32
33    if rank == 2 {
34        let messages = ring.poll().unwrap();
35        assert_eq!(messages.len(), 2);
36        assert_eq!(messages[0].origin, 0);
37        assert_eq!(messages[0].data, vec![0, 1]);
38        assert_eq!(messages[1].origin, 1);
39        assert_eq!(messages[1].data, vec![1, 1]);
40        ring.ack(0, 1).unwrap();
41        ring.ack(1, 1).unwrap();
42        ring.send(0, &[2, 1]).unwrap();
43        ring.send(1, &[2, 1]).unwrap();
44    }
45    world.barrier();
46
47    if rank < 2 {
48        let messages = ring.poll().unwrap();
49        assert_eq!(
50            messages,
51            vec![Message {
52                origin: 2,
53                sequence: 1,
54                data: vec![2, 1]
55            }]
56        );
57        ring.ack(2, 1).unwrap();
58    }
59    world.barrier();
60
61    ring.close().unwrap();
62}
More examples
Hide additional examples
examples/test_ring.rs (line 38)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146    if rank == 0 {
147        assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148    }
149    world.barrier();
150    if rank == 1 {
151        let messages = ring.poll().unwrap();
152        assert_eq!(messages.len(), 1);
153        assert_eq!(messages[0].origin, 0);
154        assert_eq!(messages[0].data, vec![7]);
155        ring.ack(0, 1).unwrap();
156    }
157    world.barrier();
158    ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162    let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163    assert!(!ring.is_safe());
164
165    for s in 1..=3u8 {
166        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167    }
168    world.barrier();
169
170    let messages = ring.poll().unwrap();
171    assert_eq!(
172        messages,
173        vec![
174            Message {
175                origin: prev,
176                sequence: 2,
177                data: vec![prev as u8, 2]
178            },
179            Message {
180                origin: prev,
181                sequence: 3,
182                data: vec![prev as u8, 3]
183            },
184        ]
185    );
186    assert_eq!(ring.lost(), 1);
187
188    // Raw acks are no-ops.
189    ring.ack(prev, u64::MAX).unwrap();
190    world.barrier();
191    ring.close().unwrap();
192}
examples/compare.rs (line 175)
137fn ring_stream(
138    comm: &SimpleCommunicator,
139    ring: &Ring,
140    rank: Rank,
141    rep: u32,
142    payload: usize,
143    n: u64,
144) -> (u64, u64, f64, f64, f64) {
145    comm.barrier();
146    let stalled = ring.wait_ns();
147    let t0 = Instant::now();
148    if rank == 0 {
149        let buf = shuffled(rep, payload);
150        let mut sent = 0u64;
151        while sent < n {
152            ring.send(1, &buf).unwrap();
153            sent += 1;
154        }
155        let inject = t0.elapsed().as_secs_f64();
156        let wait = (ring.wait_ns() - stalled) as f64 * 1e-9;
157        // Tell the drain side to stop, then collect what it actually saw.
158        comm.process_at_rank(1).send_with_tag(&[0u8], TAG_STOP);
159        let (reply, _) = comm
160            .process_at_rank(1)
161            .receive_vec_with_tag::<u64>(TAG_STOP);
162        (sent, reply[0], inject, t0.elapsed().as_secs_f64(), wait)
163    } else {
164        let mut got = 0u64;
165        let mut stopping = false;
166        loop {
167            let batch = ring.poll().unwrap();
168            let empty = batch.is_empty();
169            let mut furthest = 0u64;
170            for m in &batch {
171                got += 1;
172                furthest = furthest.max(m.sequence);
173            }
174            if furthest > 0 {
175                ring.ack(0, furthest).unwrap();
176            }
177            if stopping && empty {
178                break;
179            }
180            if !stopping
181                && comm
182                    .any_process()
183                    .immediate_probe_with_tag(TAG_STOP)
184                    .is_some()
185            {
186                comm.any_process().receive_vec_with_tag::<u8>(TAG_STOP);
187                stopping = true;
188            }
189            if empty {
190                std::thread::yield_now();
191            }
192        }
193        comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
194        let dt = t0.elapsed().as_secs_f64();
195        (0, got, dt, dt, 0.0)
196    }
197}
198
199/// One message each way, `iters` times, on the ring.
200///
201/// Returns `(total, max_us)`: the run wall time and the worst single
202/// round trip, halved to a one-way latency so it shares the `us_per_msg`
203/// unit of the mean.
204fn ring_rtt(ring: &Ring, rank: Rank, rep: u32, payload: usize, iters: u64) -> (f64, f64) {
205    let buf = shuffled(rep, payload);
206    let other: Rank = if rank == 0 { 1 } else { 0 };
207    let mut seen = 0u64;
208    let await_one = |ring: &Ring, seen: &mut u64| {
209        loop {
210            let batch = ring.poll().unwrap();
211            if let Some(last) = batch.last() {
212                *seen = last.sequence;
213                ring.ack(other, *seen).unwrap();
214                return;
215            }
216            std::hint::spin_loop();
217        }
218    };
219    let mut max_us = 0.0f64;
220    let t0 = Instant::now();
221    for _ in 0..iters {
222        let start = Instant::now();
223        if rank == 0 {
224            ring.send(1, &buf).unwrap();
225            await_one(ring, &mut seen);
226        } else {
227            await_one(ring, &mut seen);
228            ring.send(0, &buf).unwrap();
229        }
230        max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
231    }
232    (t0.elapsed().as_secs_f64(), max_us)
233}
examples/soak.rs (line 220)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}
Source

pub fn close(self) -> Result<(), Error>

Close the underlying windows. Collective over the ring communicator.

Prefer this over relying on Drop: the destructor’s shutdown runs the same steps but the collective boundary is implicit.

Examples found in repository?
examples/test_layout.rs (line 61)
20fn run(world: &SimpleCommunicator, safe: bool) {
21    let rank = world.rank();
22    let ring = if safe {
23        Ring::safe(world, &lanes(2, 8)).unwrap()
24    } else {
25        Ring::raw(world, &lanes(2, 8)).unwrap()
26    };
27
28    if rank < 2 {
29        ring.send(2, &[rank as u8, 1]).unwrap();
30    }
31    world.barrier();
32
33    if rank == 2 {
34        let messages = ring.poll().unwrap();
35        assert_eq!(messages.len(), 2);
36        assert_eq!(messages[0].origin, 0);
37        assert_eq!(messages[0].data, vec![0, 1]);
38        assert_eq!(messages[1].origin, 1);
39        assert_eq!(messages[1].data, vec![1, 1]);
40        ring.ack(0, 1).unwrap();
41        ring.ack(1, 1).unwrap();
42        ring.send(0, &[2, 1]).unwrap();
43        ring.send(1, &[2, 1]).unwrap();
44    }
45    world.barrier();
46
47    if rank < 2 {
48        let messages = ring.poll().unwrap();
49        assert_eq!(
50            messages,
51            vec![Message {
52                origin: 2,
53                sequence: 1,
54                data: vec![2, 1]
55            }]
56        );
57        ring.ack(2, 1).unwrap();
58    }
59    world.barrier();
60
61    ring.close().unwrap();
62}
More examples
Hide additional examples
examples/test_ring.rs (line 79)
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22    // Depth 4 against 3 messages: this covers ordering and acknowledge
23    // validation with the overwrite gate deliberately out of the way.
24    // Backpressure is its own test, on a one-way lane.
25    let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26    assert!(ring.is_safe());
27    assert_eq!(ring.depth(next), Some(4));
28    assert_eq!(ring.capacity(next), Some(8));
29
30    assert!(matches!(
31        ring.send(next, &[0; 9]),
32        Err(Error::Payload {
33            len: 9,
34            capacity: 8
35        })
36    ));
37    assert!(matches!(
38        ring.ack(prev, 1),
39        Err(Error::Ack {
40            sequence: 1,
41            received: 0,
42            ..
43        })
44    ));
45
46    for s in 1..=3u8 {
47        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48    }
49    world.barrier();
50
51    let messages = ring.poll().unwrap();
52    assert_eq!(
53        messages,
54        (1..=3)
55            .map(|s| Message {
56                origin: prev,
57                sequence: s,
58                data: vec![prev as u8, s as u8],
59            })
60            .collect::<Vec<_>>()
61    );
62    assert!(ring.poll().unwrap().is_empty());
63    assert_eq!(ring.lost(), 0);
64    assert_eq!(ring.max_lag(), 3);
65
66    assert!(matches!(
67        ring.ack(prev, 4),
68        Err(Error::Ack {
69            sequence: 4,
70            received: 3,
71            ..
72        })
73    ));
74    ring.ack(prev, 3).unwrap();
75    ring.ack(prev, 3).unwrap();
76    ring.ack(prev, 2).unwrap();
77
78    world.barrier();
79    ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90    if rank == 0 {
91        assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92        assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93    }
94    world.barrier();
95
96    if rank == 0 {
97        let sent = thread::scope(|scope| {
98            let sender = &ring;
99            let handle = scope.spawn(move || sender.send(1, &[3]));
100            // Depth is 2 and nothing has been acknowledged, so there is no slot
101            // for the third message and the send has to wait.
102            let deadline = Instant::now() + Duration::from_secs(5);
103            while ring.waits() == 0 {
104                assert!(Instant::now() < deadline, "safe sender never blocked");
105                thread::yield_now();
106            }
107            // The receiver is parked here, so it cannot have drained early.
108            world.barrier();
109            handle.join().expect("sender thread panicked").unwrap()
110        });
111        assert_eq!(sent, 3);
112        assert!(ring.waits() > 0);
113        assert!(ring.wait_ns() > 0);
114    } else {
115        world.barrier();
116        let messages = ring.poll().unwrap();
117        assert_eq!(messages.len(), 2);
118        assert_eq!(messages[0].data, vec![1]);
119        assert_eq!(messages[1].data, vec![2]);
120        assert!(matches!(
121            ring.ack(0, 3),
122            Err(Error::Ack {
123                sequence: 3,
124                received: 2,
125                ..
126            })
127        ));
128        ring.ack(0, 2).unwrap();
129        ring.ack(0, 2).unwrap();
130    }
131
132    world.barrier();
133    if rank == 1 {
134        let messages = ring.poll().unwrap();
135        assert_eq!(messages.len(), 1);
136        assert_eq!(messages[0].sequence, 3);
137        assert_eq!(messages[0].data, vec![3]);
138        ring.ack(0, 3).unwrap();
139    }
140    world.barrier();
141    ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145    let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146    if rank == 0 {
147        assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148    }
149    world.barrier();
150    if rank == 1 {
151        let messages = ring.poll().unwrap();
152        assert_eq!(messages.len(), 1);
153        assert_eq!(messages[0].origin, 0);
154        assert_eq!(messages[0].data, vec![7]);
155        ring.ack(0, 1).unwrap();
156    }
157    world.barrier();
158    ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162    let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163    assert!(!ring.is_safe());
164
165    for s in 1..=3u8 {
166        assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167    }
168    world.barrier();
169
170    let messages = ring.poll().unwrap();
171    assert_eq!(
172        messages,
173        vec![
174            Message {
175                origin: prev,
176                sequence: 2,
177                data: vec![prev as u8, 2]
178            },
179            Message {
180                origin: prev,
181                sequence: 3,
182                data: vec![prev as u8, 3]
183            },
184        ]
185    );
186    assert_eq!(ring.lost(), 1);
187
188    // Raw acks are no-ops.
189    ring.ack(prev, u64::MAX).unwrap();
190    world.barrier();
191    ring.close().unwrap();
192}
examples/compare.rs (line 448)
337fn main() {
338    let mut args = std::env::args().skip(1);
339    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
340    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(32);
341    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
342    let repeats: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(5);
343
344    let (mut universe, provided) =
345        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
346    assert_eq!(provided, Threading::Multiple);
347    let world = universe.world();
348    let rank = world.rank();
349    let size = world.size();
350    assert!(size >= 2, "compare needs at least 2 ranks");
351    assert!(noise_kib == 0 || size >= 4, "noise needs at least 4 ranks");
352    // `p2p-bsend` needs somewhere to copy to. Only rank 0 sends buffered, so
353    // only it attaches the buffer; every rank allocating 512 MiB would need
354    // tens of GiB at scale. Sized past the largest amount a stream can leave
355    // in flight, so the comparison measures the transport and not a buffer
356    // running out.
357    if rank == 0 {
358        universe.set_buffer_size(BSEND_BUF);
359    }
360
361    let stop = Arc::new(AtomicBool::new(false));
362    let noisy = noise_kib > 0;
363
364    std::thread::scope(|scope| {
365        if noisy && rank >= 2 {
366            let stop = Arc::clone(&stop);
367            let shared = Shared(&world);
368            scope.spawn(move || {
369                let shared = shared;
370                noise(shared.0, noise_kib, &stop);
371            });
372        }
373        if rank > 1 {
374            // Noise ranks take no part in the measurement. They wait here for
375            // rank 0 to call time, then let their noise thread finish.
376            world
377                .process_at_rank(0)
378                .receive_vec_with_tag::<u8>(TAG_QUIT);
379            stop.store(true, Ordering::Relaxed);
380            return;
381        }
382
383        if rank == 0 {
384            println!("{HEADER}");
385        }
386
387        // The measured pair only. A world barrier here would deadlock against
388        // ranks sitting in the noise loop.
389        let pair: Vec<Rank> = vec![0, 1];
390        let group = world.group().include(&pair);
391        let duo = world
392            .split_by_subgroup(&group)
393            .expect("ranks 0 and 1 form the measured pair");
394
395        for rep in 0..repeats {
396            for payload in SIZES {
397                // Both ring modes share the lane shape, so the only difference
398                // between the two rows is the acknowledgement gate.
399                let lanes = vec![
400                    (0 as Rank, 1 as Rank, depth, payload),
401                    (1, 0, depth, payload),
402                ];
403                // Cap the stream by bytes as well as count: 256 KiB x 20k would be
404                // 5 GiB through the wire for one row.
405                let n = messages.min((STREAM_BYTES / payload as u64).max(64));
406                let iters = if payload >= 32768 { 500 } else { 2000 };
407
408                for (name, safe) in [("ring-safe", true), ("ring-raw", false)] {
409                    let ring = if safe {
410                        Ring::safe(&duo, &lanes).unwrap()
411                    } else {
412                        Ring::raw(&duo, &lanes).unwrap()
413                    };
414                    let (sent, got, inject, total, wait) =
415                        ring_stream(&duo, &ring, rank, rep, payload, n);
416                    if rank == 0 {
417                        Row {
418                            transport: name,
419                            measure: "stream",
420                            payload,
421                            sent,
422                            delivered: got,
423                            inject,
424                            total,
425                            wait,
426                            max_us: 0.0,
427                        }
428                        .print(size, depth, noise_kib, rep);
429                    }
430                    duo.barrier();
431                    let stalled = ring.wait_ns();
432                    let (dt, max_us) = ring_rtt(&ring, rank, rep, payload, iters);
433                    if rank == 0 {
434                        Row {
435                            transport: name,
436                            measure: "rtt",
437                            payload,
438                            sent: iters * 2,
439                            delivered: iters * 2,
440                            inject: dt,
441                            total: dt,
442                            wait: (ring.wait_ns() - stalled) as f64 * 1e-9,
443                            max_us,
444                        }
445                        .print(size, depth, noise_kib, rep);
446                    }
447                    duo.barrier();
448                    ring.close().unwrap();
449                }
450
451                for (name, buffered) in [("p2p-send", false), ("p2p-bsend", true)] {
452                    let (sent, got, inject, total) =
453                        p2p_stream(&duo, rank, rep, payload, n, buffered);
454                    if rank == 0 {
455                        Row {
456                            transport: name,
457                            measure: "stream",
458                            payload,
459                            sent,
460                            delivered: got,
461                            inject,
462                            total,
463                            wait: 0.0,
464                            max_us: 0.0,
465                        }
466                        .print(size, depth, noise_kib, rep);
467                    }
468                    duo.barrier();
469                }
470                let (dt, max_us) = p2p_rtt(&duo, rank, rep, payload, iters);
471                if rank == 0 {
472                    Row {
473                        transport: "p2p-send",
474                        measure: "rtt",
475                        payload,
476                        sent: iters * 2,
477                        delivered: iters * 2,
478                        inject: dt,
479                        total: dt,
480                        wait: 0.0,
481                        max_us,
482                    }
483                    .print(size, depth, noise_kib, rep);
484                }
485                duo.barrier();
486            }
487        }
488
489        if rank == 0 {
490            for r in 2..size {
491                world.process_at_rank(r).send_with_tag(&[0u8], TAG_QUIT);
492            }
493        }
494    });
495
496    // Buffered sends must be drained before MPI tears the buffer down.
497    world.barrier();
498}
examples/soak.rs (line 324)
121fn main() {
122    let mut args = std::env::args().skip(1);
123    let mode = args.next().unwrap_or_else(|| "safe".into());
124    let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125    let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126    let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127    let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128    let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129    // Echoed straight into the output so repeats of one point stay distinguishable.
130    let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131    assert!(
132        payload >= STAMP,
133        "payload must hold the origin and sequence"
134    );
135    let safe = match mode.as_str() {
136        "safe" => true,
137        "raw" => false,
138        other => panic!("mode must be safe or raw, got {other:?}"),
139    };
140
141    let (universe, provided) =
142        mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143    assert_eq!(provided, Threading::Multiple);
144    let world = universe.world();
145    let rank = world.rank();
146    let size = world.size();
147    assert!(size >= 2, "soak needs at least 2 ranks");
148
149    // All-to-all: every ordered pair of distinct ranks gets a lane.
150    let mut lanes = Vec::new();
151    for source in 0..size {
152        for destination in 0..size {
153            if source != destination {
154                lanes.push((source, destination, depth, payload));
155            }
156        }
157    }
158    let ring = Arc::new(if safe {
159        Ring::safe(&world, &lanes).unwrap()
160    } else {
161        Ring::raw(&world, &lanes).unwrap()
162    });
163
164    let stop = Arc::new(AtomicBool::new(false));
165    let done = Arc::new(AtomicBool::new(false));
166    let count = Arc::new(AtomicU64::new(0));
167    let peers = (size - 1) as u64;
168    let sent = messages * peers;
169
170    world.barrier();
171    let started = std::time::Instant::now();
172
173    let outcome = std::thread::scope(|scope| {
174        if noise_kib > 0 {
175            let stop = Arc::clone(&stop);
176            let shared = Shared(&world);
177            scope.spawn(move || {
178                // Name the wrapper as a whole first. Closures capture the
179                // narrowest place they use, and `shared.0` alone would be
180                // captured as a bare reference, losing the `Send` impl.
181                let shared = shared;
182                noise(shared.0, noise_kib, &stop);
183            });
184        }
185
186        let poller = {
187            let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188            scope.spawn(move || -> Result<Vec<u64>, String> {
189                let mut seen = vec![0u64; size as usize];
190                let mut total = 0u64;
191                loop {
192                    // If true, this poll follows the global send barrier.
193                    let finished = done.load(Ordering::Acquire);
194                    let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195                    let empty = batch.is_empty();
196                    let mut furthest: HashMap<Rank, u64> = HashMap::new();
197                    for m in &batch {
198                        verify(m)?;
199                        let previous = seen[m.origin as usize];
200                        if m.sequence <= previous {
201                            return Err(format!(
202                                "sequence from {} went backwards: {} after {previous}",
203                                m.origin, m.sequence
204                            ));
205                        }
206                        if safe && m.sequence != previous + 1 {
207                            return Err(format!(
208                                "safe lane from {} skipped {} to {}",
209                                m.origin,
210                                previous + 1,
211                                m.sequence
212                            ));
213                        }
214                        seen[m.origin as usize] = m.sequence;
215                        let f = furthest.entry(m.origin).or_insert(0);
216                        *f = (*f).max(m.sequence);
217                        total += 1;
218                    }
219                    for (origin, sequence) in furthest {
220                        ring.ack(origin, sequence)
221                            .map_err(|e| format!("ack to {origin}: {e}"))?;
222                    }
223                    count.store(total, Ordering::Relaxed);
224                    if finished && empty {
225                        return Ok(seen);
226                    }
227                    if empty {
228                        std::thread::yield_now();
229                    }
230                }
231            })
232        };
233
234        // Round-robin over peers so every outgoing lane stays hot at once.
235        let mut buf = vec![0u8; payload];
236        let pace = std::time::Duration::from_nanos(pace_ns);
237        let opened = std::time::Instant::now();
238        for sequence in 1..=messages {
239            for destination in (0..size).filter(|&r| r != rank) {
240                paint(rank, sequence, &mut buf);
241                ring.send(destination, &buf)
242                    .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243            }
244            // Absolute deadlines, so a slow round is not paid for twice and the
245            // offered rate stays the one that was asked for.
246            if pace_ns > 0 {
247                let due = opened + pace * sequence as u32;
248                while std::time::Instant::now() < due {
249                    std::hint::spin_loop();
250                }
251            }
252        }
253        // Every rank's sends have completed at their targets before this
254        // returns, so after the barrier nothing new can appear in a slot.
255        world.barrier();
256        done.store(true, Ordering::Release);
257
258        let seen = poller.join().expect("poller thread panicked");
259        stop.store(true, Ordering::Relaxed);
260        seen
261    });
262
263    let elapsed = started.elapsed().as_secs_f64();
264    let fail = |why: String| -> ! {
265        eprintln!("[soak] FAIL rank {rank}: {why}");
266        world.abort(1)
267    };
268    let seen = outcome.unwrap_or_else(|why| fail(why));
269
270    let got = count.load(Ordering::Relaxed);
271    let lost = ring.lost();
272    let corrupt = ring.corrupt();
273
274    // The last message on a lane is never overwritten, so every lane must end
275    // exactly at the sender's high-water mark in both modes.
276    for origin in (0..size).filter(|&r| r != rank) {
277        if seen[origin as usize] != messages {
278            fail(format!(
279                "lane from {origin} ended at {} not {messages}",
280                seen[origin as usize]
281            ));
282        }
283    }
284    if safe && lost != 0 {
285        fail(format!("safe ring lost {lost}"));
286    }
287    if got + lost != sent {
288        fail(format!("received {got} + lost {lost} != sent {sent}"));
289    }
290
291    // Gather the counters and let rank 0 write every row. Interleaving the
292    // ranks' own stdout would scatter the header into the middle of the table:
293    // mpirun orders a rank's output against itself and nothing else.
294    let mine = [
295        got,
296        lost,
297        corrupt,
298        ring.max_lag(),
299        ring.waits(),
300        ring.wait_ns(),
301    ];
302    let mut all = vec![0u64; mine.len() * size as usize];
303    world.all_gather_into(&mine[..], &mut all[..]);
304    if rank == 0 {
305        println!(
306            "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307        );
308        for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309            println!(
310                "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311                row[0],
312                row[1],
313                row[2],
314                row[3],
315                row[4],
316                row[5] as f64 * 1e-9,
317            );
318        }
319    }
320
321    world.barrier();
322    Arc::try_unwrap(ring)
323        .unwrap_or_else(|_| panic!("ring still shared at close"))
324        .close()
325        .unwrap();
326    if rank == 0 {
327        eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328    }
329}

Auto Trait Implementations§

§

impl !Freeze for Ring

§

impl RefUnwindSafe for Ring

§

impl Send for Ring

§

impl Sync for Ring

§

impl Unpin for Ring

§

impl UnsafeUnpin for Ring

§

impl UnwindSafe for Ring

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<Src, Scheme> ApproxFrom<Src, Scheme> for Src
where Scheme: ApproxScheme,

Source§

type Err = NoError

The error type produced by a failed conversion.
Source§

fn approx_from(src: Src) -> Result<Src, <Src as ApproxFrom<Src, Scheme>>::Err>

Convert the given value into an approximately equivalent representation.
Source§

impl<Dst, Src, Scheme> ApproxInto<Dst, Scheme> for Src
where Dst: ApproxFrom<Src, Scheme>, Scheme: ApproxScheme,

Source§

type Err = <Dst as ApproxFrom<Src, Scheme>>::Err

The error type produced by a failed conversion.
Source§

fn approx_into(self) -> Result<Dst, <Src as ApproxInto<Dst, Scheme>>::Err>

Convert the subject into an approximately equivalent representation.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T, Dst> ConvAsUtil<Dst> for T

Source§

fn approx(self) -> Result<Dst, Self::Err>
where Self: Sized + ApproxInto<Dst>,

Approximate the subject with the default scheme.
Source§

fn approx_by<Scheme>(self) -> Result<Dst, Self::Err>
where Self: Sized + ApproxInto<Dst, Scheme>, Scheme: ApproxScheme,

Approximate the subject with a specific scheme.
Source§

impl<T> ConvUtil for T

Source§

fn approx_as<Dst>(self) -> Result<Dst, Self::Err>
where Self: Sized + ApproxInto<Dst>,

Approximate the subject to a given type with the default scheme.
Source§

fn approx_as_by<Dst, Scheme>(self) -> Result<Dst, Self::Err>
where Self: Sized + ApproxInto<Dst, Scheme>, Scheme: ApproxScheme,

Approximate the subject to a given type with a specific scheme.
Source§

fn into_as<Dst>(self) -> Dst
where Self: Sized + Into<Dst>,

Convert the subject to a given type.
Source§

fn try_as<Dst>(self) -> Result<Dst, Self::Err>
where Self: Sized + TryInto<Dst>,

Attempt to convert the subject to a given type.
Source§

fn value_as<Dst>(self) -> Result<Dst, Self::Err>
where Self: Sized + ValueInto<Dst>,

Attempt a value conversion of the subject to a given type.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<Src> TryFrom<Src> for Src

Source§

type Err = NoError

The error type produced by a failed conversion.
Source§

fn try_from(src: Src) -> Result<Src, <Src as TryFrom<Src>>::Err>

Convert the given value into the subject type.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<Src, Dst> TryInto<Dst> for Src
where Dst: TryFrom<Src>,

Source§

type Err = <Dst as TryFrom<Src>>::Err

The error type produced by a failed conversion.
Source§

fn try_into(self) -> Result<Dst, <Src as TryInto<Dst>>::Err>

Convert the subject into the destination type.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<Src> ValueFrom<Src> for Src

Source§

type Err = NoError

The error type produced by a failed conversion.
Source§

fn value_from(src: Src) -> Result<Src, <Src as ValueFrom<Src>>::Err>

Convert the given value into an exactly equivalent representation.
Source§

impl<Src, Dst> ValueInto<Dst> for Src
where Dst: ValueFrom<Src>,

Source§

type Err = <Dst as ValueFrom<Src>>::Err

The error type produced by a failed conversion.
Source§

fn value_into(self) -> Result<Dst, <Src as ValueInto<Dst>>::Err>

Convert the subject into an exactly equivalent representation.