Skip to main content

ph_eventing/
latest_buf.rs

1//! Freshness-first SPSC snapshot channel.
2//!
3//! [`LatestBuf`] retains at most one unread publication. A producer always
4//! publishes the newest complete `T`; if an older unread value is displaced,
5//! [`PublishReport::replaced_unread`] reports it. The consumer takes only the
6//! latest value and receives its generation plus the exact number skipped.
7//!
8//! The channel uses three slots with exclusive ownership: one producer
9//! slot, one consumer slot, and one slot named by an atomic exchange state.
10//! An endpoint accesses a slot only after acquiring it through a single
11//! atomic swap. Producer and consumer therefore never touch the same payload
12//! bytes concurrently, unlike a seqlock.
13//!
14//! Endpoint cursors live in the channel rather than the handles. Handles are
15//! stateless and dropping/reacquiring one continues its slot ownership,
16//! generation sequence, and skipped accounting.
17//!
18//! An empty consumer poll first Acquire-loads the ready bit and returns without
19//! an atomic read-modify-write. A pending poll still performs the same `AcqRel`
20//! swap that transfers slot ownership. The private initial role indices are
21//! encoded as zero so a const-initialized channel lands in `.bss` rather than
22//! carrying its three payload slots in the flash-backed `.data` image.
23//!
24//! # Decision status
25//!
26//! Decision D1 (wrap-ambiguity policy) is **closed** as documented
27//! approximation plus payload escape hatch (contract §9, non-promise X6):
28//! skipped counts are exact while fewer than `u32::MAX` non-zero
29//! generations separate successful takes, and beyond that full wrap span
30//! the `u32` result is a documented under-count —
31//! [`LatestItem::skipped`] carries the full disclosure.
32//!
33//! Decision D2 (`Source<T>` policy) is **closed**: [`Consumer`] does not
34//! implement [`crate::Source`], because `try_pop` cannot report the
35//! displacement that is this channel's *designed* overload behaviour —
36//! [`crate::LatestSource`] is the consumer's designed contract surface
37//! (contract §9, non-promise X7), and the absent impl is pinned by a
38//! `compile_fail` doctest on [`Consumer`].
39//!
40//! Decision D3 (first deliverable form) is **closed**: `T` stays generic
41//! by decision, not default — a complete block is a payload
42//! (`LatestBuf<Block<T, N>>` via the BlockBuf composition),
43//! sample-versus-block is release scheduling and RAM, and no separate
44//! latest-block type exists (contract §9).
45//!
46//! Review caveat A.3 (handle-state continuation) is **closed** the same
47//! way this implementation works: role state is channel-resident in
48//! role-owned storage and handles are stateless, so a drop-and-reacquire
49//! continues by construction (contract H4; proposal Appendix A.3).
50//! Contract non-promise X8 states the role-recovery boundary: the role is
51//! held until the handle is dropped, handle lifetime is an application
52//! property, and there is deliberately no out-of-band role reset.
53//!
54//! Every LatestBuf decision (D1–D3, A.3) is closed; the contract §9 and
55//! proposal Appendix A.3 carry the records.
56//!
57//! # Example
58//!
59//! ```
60//! use ph_eventing::LatestBuf;
61//!
62//! let channel = LatestBuf::<u32>::new();
63//! let producer = channel.try_producer().expect("producer");
64//! let consumer = channel.try_consumer().expect("consumer");
65//!
66//! assert!(!producer.publish(10).replaced_unread);
67//! assert!(producer.publish(20).replaced_unread);
68//! let item = consumer.take_latest().expect("latest value");
69//! assert_eq!((item.value, item.generation, item.skipped), (20, 2, 1));
70//! ```
71
72use crate::sync::{AtomicBool, AtomicU32, Ordering, TrackedCell};
73use core::cell::Cell;
74use core::marker::PhantomData;
75use core::mem::MaybeUninit;
76
77const SLOT_MASK: u32 = 0b11;
78const READY_BIT: u32 = 0b100;
79// Encode the initially owned slots as zero so a const-initialized channel is
80// an all-zero image and lands in `.bss`. XOR is its own inverse, and these
81// role-owned fields never enter the shared exchange state.
82const PRODUCER_SLOT_XOR: u32 = 1;
83const CONSUMER_SLOT_XOR: u32 = 2;
84
85#[derive(Clone, Copy)]
86struct Entry<T: Copy> {
87    generation: u32,
88    value: T,
89}
90
91#[derive(Clone, Copy)]
92struct ProducerState {
93    back_encoded: u32,
94    next_generation: u32,
95}
96
97#[derive(Clone, Copy)]
98struct ConsumerState {
99    front_encoded: u32,
100    last_generation: u32,
101}
102
103#[cfg(not(loom))]
104const fn slot_array<T: Copy>() -> [TrackedCell<MaybeUninit<Entry<T>>>; 3] {
105    [const { TrackedCell::new(MaybeUninit::uninit()) }; 3]
106}
107
108#[cfg(loom)]
109fn slot_array<T: Copy>() -> [TrackedCell<MaybeUninit<Entry<T>>>; 3] {
110    core::array::from_fn(|_| TrackedCell::new(MaybeUninit::uninit()))
111}
112
113/// Result of one successful latest-value publication.
114#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115#[must_use]
116pub struct PublishReport {
117    /// Non-zero transport generation assigned to this publication.
118    pub generation: u32,
119    /// Whether this publication displaced an unread older publication.
120    pub replaced_unread: bool,
121}
122
123/// A value claimed from a [`LatestBuf`].
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125#[must_use]
126pub struct LatestItem<T> {
127    /// Complete value copied from the claimed publication.
128    pub value: T,
129    /// Non-zero transport generation assigned when the value was published.
130    pub generation: u32,
131    /// Publications assigned after the prior take and before this one.
132    ///
133    /// One formula in every case (contract C3): the wrap-aware generation
134    /// distance from the previously taken value to this one, minus one,
135    /// saturated at zero. Exact while fewer than `u32::MAX` non-zero
136    /// generations — one full wrap span — separate two successful takes.
137    /// Beyond that span the same formula under-counts: by whole spans
138    /// while the endpoints differ, and a gap of exactly one or more full
139    /// cycles reports zero — a silent under-count, accepted and
140    /// documented by decision D1 (contract non-promise X6).
141    ///
142    /// The boundary is a rate × take-interval property: it is crossed
143    /// exactly when a full span of publications occurs between two
144    /// successful takes — whether because the consumer stopped (fault)
145    /// or because the deployment deliberately takes rarely against a
146    /// fast producer (design). At representative rates that is roughly
147    /// 49.7 days between takes at continuous 1 kHz publishing, or
148    /// 72 minutes at 1 MHz; the crate bounds neither rate nor cadence,
149    /// so this arithmetic is the caller's to run. The channel
150    /// deliberately does not detect the crossing; detection would price
151    /// wider hot-path state into every operation and still could not
152    /// recover the lost count. If the count itself is the requirement
153    /// (audit, metering, loss accounting), carry a wider
154    /// producer-assigned sequence inside `T`; if consumer liveness is,
155    /// use a watchdog — each catches its condition sooner and correctly.
156    pub skipped: u32,
157}
158
159/// Three-slot freshness-first SPSC snapshot channel.
160///
161/// `LatestBuf<T>` has fixed storage for three `T` values and never allocates.
162/// Publishing always succeeds and takes one atomic exchange. Taking is also
163/// bounded to one load, at most one exchange, and at most one payload copy.
164pub struct LatestBuf<T: Copy> {
165    exchange: AtomicU32,
166    slots: [TrackedCell<MaybeUninit<Entry<T>>>; 3],
167    producer_state: TrackedCell<ProducerState>,
168    consumer_state: TrackedCell<ConsumerState>,
169    producer_taken: AtomicBool,
170    consumer_taken: AtomicBool,
171}
172
173// SAFETY: the handle-acquisition flags enforce one endpoint per role. Each
174// payload slot belongs exclusively to the producer, consumer, or atomic
175// exchange state, and AcqRel swaps transfer both ownership and visibility.
176// The role-state cells are accessed only by their unique active handle; the
177// Release drop / AcqRel acquisition pair orders access across reacquisition.
178unsafe impl<T: Copy + Send> Sync for LatestBuf<T> {}
179
180impl<T: Copy> LatestBuf<T> {
181    /// Create an empty channel.
182    ///
183    /// On normal builds this is `const`, so a channel may live in a `static`.
184    /// Under `--cfg loom` it is non-const because Loom's primitives are not
185    /// const-constructible.
186    #[cfg(not(loom))]
187    pub const fn new() -> Self {
188        Self {
189            exchange: AtomicU32::new(0),
190            slots: slot_array(),
191            producer_state: TrackedCell::new(ProducerState {
192                back_encoded: 0,
193                next_generation: 0,
194            }),
195            consumer_state: TrackedCell::new(ConsumerState {
196                front_encoded: 0,
197                last_generation: 0,
198            }),
199            producer_taken: AtomicBool::new(false),
200            consumer_taken: AtomicBool::new(false),
201        }
202    }
203
204    /// Create an empty channel for Loom model checking.
205    #[cfg(loom)]
206    pub fn new() -> Self {
207        Self {
208            exchange: AtomicU32::new(0),
209            slots: slot_array(),
210            producer_state: TrackedCell::new(ProducerState {
211                back_encoded: 0,
212                next_generation: 0,
213            }),
214            consumer_state: TrackedCell::new(ConsumerState {
215                front_encoded: 0,
216                last_generation: 0,
217            }),
218            producer_taken: AtomicBool::new(false),
219            consumer_taken: AtomicBool::new(false),
220        }
221    }
222
223    /// Try to acquire the unique producer role.
224    ///
225    /// Returns `None` while another producer handle is active. Dropping the
226    /// active handle makes the role available without resetting its state.
227    ///
228    /// The role is held until the active handle is *dropped* — a handle
229    /// that is forgotten, or owned by an execution context destroyed
230    /// without running destructors, leaves the role held with channel
231    /// state intact. There is deliberately no out-of-band role reset: a
232    /// forced release could free a role while a live handle still exists,
233    /// defeating the exclusive ownership that soundness rests on
234    /// (contract non-promise X8). Handle lifetime is the application's
235    /// property; the channel observes only acquisition and drop.
236    #[inline]
237    pub fn try_producer(&self) -> Option<Producer<'_, T>> {
238        if self.producer_taken.swap(true, Ordering::AcqRel) {
239            None
240        } else {
241            Some(Producer {
242                buf: self,
243                _not_sync: PhantomData,
244            })
245        }
246    }
247
248    /// Try to acquire the unique consumer role.
249    ///
250    /// Returns `None` while another consumer handle is active. Dropping the
251    /// active handle makes the role available without resetting its state.
252    ///
253    /// Role recovery follows the same boundary as [`Self::try_producer`]:
254    /// held until dropped, no out-of-band reset, handle lifetime owned by
255    /// the application (contract non-promise X8).
256    #[inline]
257    pub fn try_consumer(&self) -> Option<Consumer<'_, T>> {
258        if self.consumer_taken.swap(true, Ordering::AcqRel) {
259            None
260        } else {
261            Some(Consumer {
262                buf: self,
263                _not_sync: PhantomData,
264            })
265        }
266    }
267
268    #[inline(always)]
269    const fn encode(slot: u32, ready: bool) -> u32 {
270        slot | if ready { READY_BIT } else { 0 }
271    }
272
273    #[inline(always)]
274    const fn slot(state: u32) -> usize {
275        (state & SLOT_MASK) as usize
276    }
277
278    #[inline(always)]
279    const fn ready(state: u32) -> bool {
280        state & READY_BIT != 0
281    }
282
283    #[inline(always)]
284    const fn producer_slot(encoded: u32) -> u32 {
285        encoded ^ PRODUCER_SLOT_XOR
286    }
287
288    #[inline(always)]
289    const fn encode_producer_slot(slot: u32) -> u32 {
290        slot ^ PRODUCER_SLOT_XOR
291    }
292
293    #[inline(always)]
294    const fn consumer_slot(encoded: u32) -> u32 {
295        encoded ^ CONSUMER_SLOT_XOR
296    }
297
298    #[inline(always)]
299    const fn encode_consumer_slot(slot: u32) -> u32 {
300        slot ^ CONSUMER_SLOT_XOR
301    }
302
303    #[inline(always)]
304    const fn next_generation(current: u32) -> u32 {
305        match current.wrapping_add(1) {
306            0 => 1,
307            generation => generation,
308        }
309    }
310
311    /// Assigned-generation distance in `(from, to]`, excluding reserved zero.
312    #[inline(always)]
313    const fn generation_distance(from: u32, to: u32) -> u32 {
314        let raw = to.wrapping_sub(from);
315        if to < from { raw - 1 } else { raw }
316    }
317}
318
319impl<T: Copy> Default for LatestBuf<T> {
320    fn default() -> Self {
321        Self::new()
322    }
323}
324
325impl<T: Copy> core::fmt::Debug for LatestBuf<T> {
326    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
327        f.debug_struct("LatestBuf").finish_non_exhaustive()
328    }
329}
330
331/// Unique, stateless write handle for a [`LatestBuf`].
332///
333/// This handle is `Send` **when `T: Send`** (the handle can move a payload
334/// across contexts, so a non-`Send` payload correctly pins it — `T: Copy`
335/// alone does not imply `T: Send`) and always `!Sync`: it may move into an
336/// ISR or another execution context, but it may not be shared between
337/// contexts. Sole-producer ownership is load-bearing for the exclusive-slot
338/// soundness argument (contract H2).
339///
340/// ```compile_fail,E0277
341/// use ph_eventing::latest_buf::Producer;
342///
343/// fn assert_sync<T: Sync>() {}
344/// assert_sync::<Producer<'static, u32>>();
345/// ```
346pub struct Producer<'a, T: Copy> {
347    buf: &'a LatestBuf<T>,
348    _not_sync: PhantomData<Cell<()>>,
349}
350
351impl<T: Copy> Producer<'_, T> {
352    /// Publish a complete value as the newest channel state.
353    ///
354    /// This always succeeds, executes one payload write and one atomic swap,
355    /// and never waits for consumer progress.
356    #[inline]
357    pub fn publish(&self, value: T) -> PublishReport {
358        // SAFETY: producer_taken grants the unique producer handle exclusive
359        // access to producer_state for its lifetime. Reacquisition is ordered
360        // by the handle's Release drop and AcqRel acquisition.
361        let (back, generation) = self.buf.producer_state.with_mut(|state| unsafe {
362            let state = &mut *state;
363            let generation = LatestBuf::<T>::next_generation(state.next_generation);
364            state.next_generation = generation;
365            (
366                LatestBuf::<T>::producer_slot(state.back_encoded),
367                generation,
368            )
369        });
370
371        // SAFETY: `back` is exclusively producer-owned. The producer does not
372        // relinquish it until the following atomic exchange.
373        self.buf.slots[back as usize].with_mut(|slot| unsafe {
374            (*slot).write(Entry { generation, value });
375        });
376
377        let previous = self
378            .buf
379            .exchange
380            .swap(LatestBuf::<T>::encode(back, true), Ordering::AcqRel);
381
382        // SAFETY: the exchange transferred its previous slot exclusively to
383        // the producer. No other producer handle exists.
384        self.buf.producer_state.with_mut(|state| unsafe {
385            (*state).back_encoded = LatestBuf::<T>::encode_producer_slot(previous & SLOT_MASK);
386        });
387
388        PublishReport {
389            generation,
390            replaced_unread: LatestBuf::<T>::ready(previous),
391        }
392    }
393}
394
395impl<T: Copy> Drop for Producer<'_, T> {
396    fn drop(&mut self) {
397        self.buf.producer_taken.store(false, Ordering::Release);
398    }
399}
400
401impl<T: Copy> core::fmt::Debug for Producer<'_, T> {
402    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
403        f.debug_struct("latest_buf::Producer").finish()
404    }
405}
406
407/// Unique, stateless read handle for a [`LatestBuf`].
408///
409/// This handle is `Send` **when `T: Send`** (a non-`Send` payload correctly
410/// pins it to one context) and always `!Sync`: it may move into a consumer
411/// context, but it may not be shared between contexts (contract H2).
412///
413/// ```compile_fail,E0277
414/// use ph_eventing::latest_buf::Consumer;
415///
416/// fn assert_sync<T: Sync>() {}
417/// assert_sync::<Consumer<'static, u32>>();
418/// ```
419///
420/// # No `Source<T>` implementation — by decision, not omission
421/// `Source::try_pop` cannot report the displacement that is this channel's
422/// designed overload behaviour, so a generic pipeline would silently
423/// discard loss evidence during *normal* operation (decision D2; contract
424/// non-promise X7). [`crate::LatestSource`] is the consumer's designed
425/// contract surface. A caller whose domain genuinely permits discarding
426/// the skipped count writes its own adapter, so the discard is signed in
427/// application code, never by the transport. This `compile_fail` doctest
428/// pins the absent impl so a convenience `Source` cannot arrive silently,
429/// and pinning the error code keeps it honest:
430///
431/// ```compile_fail,E0277
432/// use ph_eventing::Source;
433/// let channel = ph_eventing::LatestBuf::<u32>::new();
434/// let mut consumer = channel.try_consumer().unwrap();
435/// let _ = Source::try_pop(&mut consumer);
436/// ```
437pub struct Consumer<'a, T: Copy> {
438    buf: &'a LatestBuf<T>,
439    _not_sync: PhantomData<Cell<()>>,
440}
441
442impl<T: Copy> Consumer<'_, T> {
443    /// Claim and copy the latest unread publication.
444    ///
445    /// Returns `None` when no publication is pending. The operation performs
446    /// an `Acquire` load first, so an empty poll returns without an atomic
447    /// read-modify-write. A pending publication is still claimed with the
448    /// ownership-transferring `AcqRel` swap.
449    #[inline]
450    pub fn take_latest(&self) -> Option<LatestItem<T>> {
451        // A false load can linearize before a concurrent publication: the
452        // publication remains pending for the next poll. This path transfers
453        // no slot and therefore must not access or update either owned role.
454        // A true load is only a hint; the AcqRel swap below remains the actual
455        // ownership transfer and may claim a newer replacement publication.
456        if !LatestBuf::<T>::ready(self.buf.exchange.load(Ordering::Acquire)) {
457            return None;
458        }
459
460        // SAFETY: consumer_taken grants this handle exclusive role-state
461        // access, ordered across reacquisition by Release/AcqRel.
462        let front = self
463            .buf
464            .consumer_state
465            .with(|state| unsafe { LatestBuf::<T>::consumer_slot((*state).front_encoded) });
466
467        let previous = self
468            .buf
469            .exchange
470            .swap(LatestBuf::<T>::encode(front, false), Ordering::AcqRel);
471        let claimed = previous & SLOT_MASK;
472
473        // SAFETY: the exchange transferred `claimed` exclusively to this
474        // consumer; preserving it even on the empty path maintains the three
475        // disjoint ownership roles.
476        self.buf.consumer_state.with_mut(|state| unsafe {
477            (*state).front_encoded = LatestBuf::<T>::encode_consumer_slot(claimed);
478        });
479
480        if !LatestBuf::<T>::ready(previous) {
481            return None;
482        }
483
484        // SAFETY: the ready bit means the producer initialized this Entry
485        // before publishing it. AcqRel swap acquired both ownership and the
486        // initialized bytes, and no producer can reacquire the slot until a
487        // later exchange relinquishes it.
488        let entry = self.buf.slots[LatestBuf::<T>::slot(previous)]
489            .with(|slot| unsafe { (*slot).assume_init_read() });
490
491        // SAFETY: this unique consumer handle exclusively owns consumer_state.
492        let skipped = self.buf.consumer_state.with_mut(|state| unsafe {
493            let state = &mut *state;
494            let distance =
495                LatestBuf::<T>::generation_distance(state.last_generation, entry.generation);
496            state.last_generation = entry.generation;
497            distance.saturating_sub(1)
498        });
499
500        Some(LatestItem {
501            value: entry.value,
502            generation: entry.generation,
503            skipped,
504        })
505    }
506}
507
508impl<T: Copy> Drop for Consumer<'_, T> {
509    fn drop(&mut self) {
510        self.buf.consumer_taken.store(false, Ordering::Release);
511    }
512}
513
514impl<T: Copy> core::fmt::Debug for Consumer<'_, T> {
515    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
516        f.debug_struct("latest_buf::Consumer").finish()
517    }
518}
519
520impl<T: Copy> crate::traits::LatestSink<T> for Producer<'_, T> {
521    #[inline]
522    fn publish_latest(&mut self, value: T) -> PublishReport {
523        self.publish(value)
524    }
525}
526
527impl<T: Copy> crate::traits::LatestSource<T> for Consumer<'_, T> {
528    #[inline]
529    fn try_take_latest(&mut self) -> Option<LatestItem<T>> {
530        self.take_latest()
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537
538    #[test]
539    fn starts_empty_and_takes_each_publication_at_most_once() {
540        let channel = LatestBuf::<u32>::new();
541        let producer = channel.try_producer().unwrap();
542        let consumer = channel.try_consumer().unwrap();
543        assert_eq!(consumer.take_latest(), None);
544        assert_eq!(producer.publish(7).generation, 1);
545        assert_eq!(
546            consumer.take_latest(),
547            Some(LatestItem {
548                value: 7,
549                generation: 1,
550                skipped: 0
551            })
552        );
553        assert_eq!(consumer.take_latest(), None);
554    }
555
556    #[test]
557    fn replacement_is_reported_on_both_endpoints() {
558        let channel = LatestBuf::<u32>::new();
559        let producer = channel.try_producer().unwrap();
560        let consumer = channel.try_consumer().unwrap();
561        assert!(!producer.publish(10).replaced_unread);
562        assert!(producer.publish(20).replaced_unread);
563        assert!(producer.publish(30).replaced_unread);
564        assert_eq!(
565            consumer.take_latest(),
566            Some(LatestItem {
567                value: 30,
568                generation: 3,
569                skipped: 2
570            })
571        );
572    }
573
574    #[test]
575    fn handle_reacquisition_continues_role_state() {
576        let channel = LatestBuf::<u32>::new();
577        {
578            let producer = channel.try_producer().unwrap();
579            assert_eq!(producer.publish(1).generation, 1);
580        }
581        let producer = channel.try_producer().unwrap();
582        assert_eq!(
583            producer.publish(2),
584            PublishReport {
585                generation: 2,
586                replaced_unread: true
587            }
588        );
589
590        {
591            let consumer = channel.try_consumer().unwrap();
592            assert_eq!(consumer.take_latest().unwrap().skipped, 1);
593        }
594        let _ = producer.publish(3);
595        let _ = producer.publish(4);
596        let consumer = channel.try_consumer().unwrap();
597        assert_eq!(
598            consumer.take_latest(),
599            Some(LatestItem {
600                value: 4,
601                generation: 4,
602                skipped: 1
603            })
604        );
605    }
606
607    #[test]
608    fn role_acquisition_is_unique_and_handles_are_send() {
609        fn assert_send<T: Send>() {}
610        assert_send::<Producer<'_, u32>>();
611        assert_send::<Consumer<'_, u32>>();
612
613        let channel = LatestBuf::<u32>::new();
614        let producer = channel.try_producer().unwrap();
615        let consumer = channel.try_consumer().unwrap();
616        assert!(channel.try_producer().is_none());
617        assert!(channel.try_consumer().is_none());
618        drop(producer);
619        drop(consumer);
620        assert!(channel.try_producer().is_some());
621        assert!(channel.try_consumer().is_some());
622    }
623
624    #[cfg(not(loom))]
625    #[test]
626    fn static_channel_yields_static_sendable_handles() {
627        static CHANNEL: LatestBuf<u32> = LatestBuf::new();
628        fn producer() -> Producer<'static, u32> {
629            CHANNEL.try_producer().unwrap()
630        }
631        fn consumer() -> Consumer<'static, u32> {
632            CHANNEL.try_consumer().unwrap()
633        }
634        let producer = producer();
635        let consumer = consumer();
636        let _ = producer.publish(42);
637        assert_eq!(consumer.take_latest().unwrap().value, 42);
638    }
639
640    #[test]
641    fn generation_wrap_skips_zero_and_counts_gap_exactly() {
642        let channel = LatestBuf::<u32>::new();
643        // SAFETY: no producer handle exists while the test seeds role state.
644        channel.producer_state.with_mut(|state| unsafe {
645            (*state).next_generation = u32::MAX - 1;
646        });
647        // SAFETY: no consumer handle exists while the test seeds role state.
648        channel.consumer_state.with_mut(|state| unsafe {
649            (*state).last_generation = u32::MAX - 1;
650        });
651        let producer = channel.try_producer().unwrap();
652        let consumer = channel.try_consumer().unwrap();
653        assert_eq!(producer.publish(1).generation, u32::MAX);
654        assert_eq!(producer.publish(2).generation, 1);
655        assert_eq!(
656            consumer.take_latest(),
657            Some(LatestItem {
658                value: 2,
659                generation: 1,
660                skipped: 1
661            })
662        );
663        assert_eq!(LatestBuf::<u32>::generation_distance(u32::MAX, 1), 1);
664    }
665
666    #[test]
667    fn full_generation_cycle_uses_documented_approximation() {
668        // Equal endpoints are indistinguishable from no progress after a full
669        // generation cycle. D1 is closed as documented approximation plus
670        // payload escape hatch (contract C3/X6); this test is the closure's
671        // named pin for the full-cycle modular result — including the take
672        // path, not only the pure distance helper.
673        assert_eq!(LatestBuf::<u32>::generation_distance(17, 17), 0);
674
675        let channel = LatestBuf::<u32>::new();
676        // SAFETY: no handles exist while the test seeds role state.
677        channel.producer_state.with_mut(|state| unsafe {
678            // Next publish assigns generation 17 (skipping reserved 0).
679            (*state).next_generation = 16;
680        });
681        // SAFETY: no handles exist while the test seeds role state.
682        channel.consumer_state.with_mut(|state| unsafe {
683            // Resume cursor already at 17: a wrap-aliased publish of 17 looks
684            // like "nothing skipped" even though a full span was lost.
685            (*state).last_generation = 17;
686        });
687        let producer = channel.try_producer().unwrap();
688        let consumer = channel.try_consumer().unwrap();
689        assert_eq!(producer.publish(99).generation, 17);
690        assert_eq!(
691            consumer.take_latest(),
692            Some(LatestItem {
693                value: 99,
694                generation: 17,
695                skipped: 0,
696            })
697        );
698    }
699
700    #[test]
701    fn generic_payload_can_be_a_complete_block() {
702        let channel = LatestBuf::<[u16; 4]>::new();
703        let producer = channel.try_producer().unwrap();
704        let consumer = channel.try_consumer().unwrap();
705        let _ = producer.publish([1, 2, 3, 4]);
706        assert_eq!(consumer.take_latest().unwrap().value, [1, 2, 3, 4]);
707    }
708
709    #[test]
710    fn concurrent_publication_never_returns_torn_value() {
711        let channel = LatestBuf::<[u32; 4]>::new();
712        let total = crate::test_support::iterations(50_000);
713        std::thread::scope(|scope| {
714            scope.spawn(|| {
715                let producer = channel.try_producer().unwrap();
716                for value in 1..=total {
717                    let _ = producer.publish([value; 4]);
718                }
719            });
720            let consumer = channel.try_consumer().unwrap();
721            let mut last_generation = 0;
722            while last_generation < total {
723                if let Some(item) = consumer.take_latest() {
724                    assert!(item.generation > last_generation);
725                    assert_eq!(item.value, [item.value[0]; 4]);
726                    last_generation = item.generation;
727                } else {
728                    std::thread::yield_now();
729                }
730            }
731        });
732    }
733}