Skip to main content

epics_base_rs/server/
event_queue.rs

1//! The CA server monitor event queue — a port of C `dbEvent.c`'s
2//! `event_user` / `event_que` / `evSubscrip` triple.
3//!
4//! # Why this exists as its own primitive
5//!
6//! The port used to model a monitor as a bounded `mpsc` channel plus a side
7//! "coalesce slot" holding the newest event once the channel filled. That
8//! shape cannot express C's queue, and three review rounds (R8-21, R8-22,
9//! R8-23) found separate divergences that all trace back to it:
10//!
11//! * the slot's value means "newer than everything queued", so a consumer that
12//!   finds it set has to discard the whole backlog — C instead replaces the
13//!   monitor's *last* queued entry in place and keeps the earlier distinct
14//!   ones (R8-22);
15//! * the queue was per-subscription, so C's `nDuplicates` — a field of the
16//!   *queue*, shared by every subscription attached to it — had no home, and
17//!   the EVENTS_OFF drain gate was evaluated per subscription (R8-23).
18//!
19//! So the primitive here is C's, not a patched channel:
20//!
21//! * [`EventUser`] == C `event_user`: one per CA circuit (C: one per
22//!   `db_init_events` client). Owns `flowCtrlMode` (EVENTS_OFF) and the chain
23//!   of queues.
24//! * [`EvQue`] == C `event_que`: a ring of `EVENTQUESIZE` entries shared by up
25//!   to `EVENTQUESIZE/EVENTENTRIES - 1` subscriptions (the `quota` rule,
26//!   `dbEvent.c:446-469`), carrying the queue-level `nDuplicates`.
27//! * `SubQ` == C `evSubscrip`: `npend` (queued entry count), `nreplace`, and
28//!   `pLastLog` — the entry a post *replaces* rather than appending.
29//!
30//! # Invariants (all enforced by [`EvQue`], the single owner)
31//!
32//! * MUST: `n_duplicates == Σ over subscriptions of max(0, npend - 1)`. Only
33//!   the append branch of [`EventSink::post`] raises it and only
34//!   `QueInner::remove_front` lowers it — C `db_queue_event_log`
35//!   (`dbEvent.c:836-840`) and `event_remove` (`dbEvent.c:542-558`).
36//! * MUST: `total_pending == Σ npend`, and `total_pending < size` — the `quota`
37//!   reservation is what guarantees the ring can never fill: past the replace
38//!   threshold each attached subscription can add at most one more entry, and
39//!   attachment is capped so that always fits (C asserts the free slot at
40//!   `dbEvent.c:833`).
41//! * MUST NOT: any pending event live outside `SubQ::events`. There is no side
42//!   slot; a monitor's newest event is `events.back()` — C's `*pLastLog` —
43//!   whether it got there by append or by in-place replace.
44//!
45//! # The two decisions, each in exactly one place
46//!
47//! * **Append or replace** — [`EventSink::post`], C `db_queue_event_log`
48//!   (`dbEvent.c:806-852`): with an entry already queued for this monitor AND
49//!   (`flowCtrlMode` OR the ring within `EVENTSPERQUE` of full), overwrite
50//!   `*pLastLog` in place; otherwise append and grow the ring.
51//! * **Drain or suspend** — [`EventReader::recv`] / [`EventReader::try_recv`],
52//!   C `event_read` (`dbEvent.c:940-952`): suspend only while `flowCtrlMode &&
53//!   nDuplicates == 0`; otherwise drain the queue to empty. Once a drain pass
54//!   starts it runs to `EVENTQEMPTY` without re-checking the gate (C's `while
55//!   (evque[getix] != EVENTQEMPTY)` loop), which is what `draining` tracks.
56//!
57//! Those two are the ONLY ways in and out of the queue: [`EvQue`] hands out no
58//! mutating method of its own, so no caller can enqueue past the replace rule
59//! or dequeue past the EVENTS_OFF gate.
60//!
61//! # Documented deviations from C
62//!
63//! * C's ring is drained in strict ring order by one event task per
64//!   `event_user`; here each subscription has its own reader task (the CA
65//!   server frames every subscription separately), so entries are taken in
66//!   per-subscription FIFO order and the *interleaving* of two subscriptions on
67//!   one circuit is not fixed. Every quantity C's queue behaviour depends on —
68//!   ring occupancy, `nDuplicates`, `quota`, per-monitor `npend`/`pLastLog` —
69//!   is shared and accounted exactly as in C.
70//! * C's `db_queue_event_log` early-drops a post when both the queued and the
71//!   incoming field log are by-reference with no copy (`dbEvent.c:794-800`).
72//!   Every event here carries an owned `Snapshot` (C `dbfl_has_copy` is always
73//!   true), so that branch cannot be reached.
74//! * When a post replaces `*pLastLog`, C frees the displaced field log and with
75//!   it its `mask`. The port ORs the displaced mask into the survivor so a
76//!   class-narrowing consumer still learns which `DBE_*` classes changed since
77//!   its last delivery (pre-existing deliberate deviation, 446e0d4a); the
78//!   surviving *value* is C's.
79
80use std::collections::HashMap;
81use std::collections::VecDeque;
82use std::sync::Mutex;
83use std::sync::atomic::{AtomicBool, Ordering};
84
85use crate::runtime::sync::{Arc, Notify};
86use crate::server::pv::MonitorEvent;
87
88/// C `EVENTENTRIES` (`dbEvent.c:62`) — ring entries reserved per attached
89/// subscription.
90pub const EVENT_ENTRIES: usize = 4;
91
92/// C `EVENTSPERQUE` (`dbEvent.c:61`) — the ring-space threshold at or below
93/// which a post replaces the monitor's last entry instead of appending, sized
94/// by C from the Ethernet MTU. Operators scale the queue with
95/// `EPICS_CAS_MAX_EVENTS_PER_CHAN`; the default is C's 36, giving C's 144-entry
96/// ring.
97pub fn events_per_que() -> usize {
98    crate::runtime::env::get("EPICS_CAS_MAX_EVENTS_PER_CHAN")
99        .and_then(|v| v.parse::<usize>().ok())
100        .filter(|n| *n >= EVENT_ENTRIES)
101        .unwrap_or(36)
102}
103
104/// C `EVENTQUESIZE` (`dbEvent.c:63`) — total ring entries in one queue.
105pub fn event_que_size() -> usize {
106    EVENT_ENTRIES * events_per_que()
107}
108
109/// C `event_user::flowCtrlMode` (`dbEvent.c:101`) — the circuit-wide EVENTS_OFF
110/// flag. Held behind its own `Arc` so an [`EvQue`] can read it without a
111/// back-pointer to the [`EventUser`] that owns the chain.
112#[derive(Default, Debug)]
113struct FlowCtrl {
114    on: AtomicBool,
115}
116
117impl FlowCtrl {
118    fn is_on(&self) -> bool {
119        self.on.load(Ordering::Acquire)
120    }
121}
122
123/// What [`EventSink::post`] did, for the caller to account for. The queue
124/// applies every change to its own state itself (it is the single owner); this
125/// reports the part that lives outside it — the dropped-monitor-event counter.
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum PostOutcome {
128    /// C append branch (`dbEvent.c:832-852`): the event took a new ring entry.
129    /// `first_event` mirrors C's `firstEventFlag` — the ring was empty before
130    /// this post.
131    Appended { first_event: bool },
132    /// C replace branch (`dbEvent.c:812-827`): the monitor already had an entry
133    /// queued and the queue is in flow control or within `EVENTSPERQUE` of full,
134    /// so `*pLastLog` was overwritten. The displaced value is never delivered —
135    /// one lost monitor event (C `nreplace`).
136    Replaced,
137    /// The subscription is gone (reader dropped, or never attached). Nothing was
138    /// queued — the `mpsc::Sender::try_send`-on-closed-channel case.
139    Closed,
140}
141
142/// C `evSubscrip` — one subscription's state inside a queue.
143struct SubQ {
144    /// C `npend` is `events.len()`; C `*pLastLog` is `events.back_mut()`. Every
145    /// pending event of this monitor lives here and nowhere else.
146    events: VecDeque<MonitorEvent>,
147    /// C `nreplace` — posts that overwrote `*pLastLog`.
148    nreplace: u64,
149    /// The producer row (`EventSink`) is gone: no further posts can arrive. The
150    /// reader still drains what is queued and then sees end-of-stream, exactly
151    /// as an `mpsc::Receiver` does when the last `Sender` drops.
152    producer_gone: bool,
153    /// The reader (`EventReader`) is gone: posts are refused from here on.
154    reader_gone: bool,
155}
156
157impl SubQ {
158    fn new() -> Self {
159        Self {
160            events: VecDeque::new(),
161            nreplace: 0,
162            producer_gone: false,
163            reader_gone: false,
164        }
165    }
166}
167
168/// The queue's mutable state. Everything here moves under one lock — C's
169/// `LOCKEVQUE`.
170struct QueInner {
171    /// Occupied ring entries, `Σ npend`. C derives this from `putix`/`getix`
172    /// (`ringSpace`); the port counts it because entries are taken in
173    /// per-subscription order (see the module's documented deviations).
174    total_pending: usize,
175    /// C `event_que::nDuplicates` (`dbEvent.c:80`) — entries queued beyond the
176    /// first for their monitor, summed over every subscription attached HERE.
177    /// This is what makes a duplicate on subscription B unblock the drain of
178    /// subscription A's entry under EVENTS_OFF.
179    n_duplicates: usize,
180    /// C `event_read`'s drain loop is in flight: it empties the queue in one
181    /// pass and does not re-consult `flowCtrlMode` per entry.
182    draining: bool,
183    /// Wakers registered by [`EvQue::poll_next`] callers parked on this
184    /// queue. The poll-based twin of the `Notify` waiter list: a consumer
185    /// that multiplexes MANY subscriptions from ONE task (the QSRV group
186    /// drain) parks here instead of holding a `Notified` future per queue.
187    /// Flushed — woken and cleared — by [`EvQue::wake_readers`], the single
188    /// owner of reader wakeup, on every transition that can make a parked
189    /// read Ready.
190    poll_wakers: Vec<std::task::Waker>,
191    subs: HashMap<u32, SubQ>,
192    /// C `event_que::quota` (`dbEvent.c:79`) — ring entries reserved by the
193    /// subscriptions attached here, `EVENT_ENTRIES` each. A subscription may
194    /// attach while `quota < size - EVENT_ENTRIES` (`dbEvent.c:453`); this is
195    /// what caps a queue at `size / EVENT_ENTRIES - 1` monitors and guarantees
196    /// the ring cannot fill.
197    quota: usize,
198    size: usize,
199    replace_threshold: usize,
200}
201
202impl QueInner {
203    /// C `ringSpace` (`dbEvent.c:136-147`) — unused ring entries.
204    fn ring_space(&self) -> usize {
205        self.size - self.total_pending
206    }
207
208    /// C `event_remove` (`dbEvent.c:542-558`): take this monitor's oldest entry
209    /// and keep `nDuplicates` / occupancy symmetric. The ONLY removal path —
210    /// the reader, subscription teardown, and queue detach all go through it, so
211    /// no caller can move one counter without the other.
212    fn remove_front(&mut self, sid: u32) -> Option<MonitorEvent> {
213        let sub = self.subs.get_mut(&sid)?;
214        let event = sub.events.pop_front()?;
215        // C: `npend == 1` ⇒ the monitor has no last log any more; otherwise the
216        // entry just removed was one of its duplicates.
217        if !sub.events.is_empty() {
218            debug_assert!(self.n_duplicates >= 1, "nDuplicates underflow");
219            self.n_duplicates -= 1;
220        }
221        self.total_pending -= 1;
222        // C's drain loop ends at `EVENTQEMPTY`.
223        self.draining = self.total_pending > 0;
224        Some(event)
225    }
226
227    /// Drop every entry a departing subscription still holds, then release its
228    /// quota. Symmetric by construction: it drains through `remove_front`
229    /// rather than zeroing counters, so `n_duplicates` / `total_pending` cannot
230    /// drift. C does the same per entry in `event_remove`, and releases the
231    /// quota once the cancelled subscription has drained (`dbEvent.c:999-1002`).
232    fn detach(&mut self, sid: u32) {
233        while self.remove_front(sid).is_some() {}
234        if self.subs.remove(&sid).is_some() {
235            self.quota -= EVENT_ENTRIES;
236        }
237    }
238
239    /// C `db_add_event`'s attach test (`dbEvent.c:451-457`): take this queue if
240    /// it still has room for one more monitor's reservation.
241    fn try_attach(&mut self, sid: u32) -> bool {
242        if self.quota >= self.size - EVENT_ENTRIES {
243            return false;
244        }
245        self.quota += EVENT_ENTRIES;
246        self.subs.insert(sid, SubQ::new());
247        true
248    }
249
250    /// May a reader take an entry right now? C `event_read`'s gate
251    /// (`dbEvent.c:947`), plus the in-pass stickiness of its drain loop.
252    fn may_drain(&self, flow_ctrl_on: bool) -> bool {
253        self.draining || !flow_ctrl_on || self.n_duplicates > 0
254    }
255}
256
257/// C `event_que` (`dbEvent.c:69-82`) — one ring, shared by every subscription
258/// attached to it.
259///
260/// Exposes no mutating method: events enter through [`EventSink::post`] and
261/// leave through [`EventReader::recv`] / [`EventReader::try_recv`], which are
262/// the sole owners of C's two decisions. The accessors below are read-only
263/// views of C's counters for diagnostics and tests.
264pub struct EvQue {
265    flow: Arc<FlowCtrl>,
266    inner: Mutex<QueInner>,
267    /// Broadcast wakeup: a post, a flow-control change, or a producer/reader
268    /// teardown re-arms every reader on this queue. C signals the one
269    /// `ppendsem` per `event_user` for the same reason.
270    wake: Notify,
271}
272
273impl EvQue {
274    fn new(flow: Arc<FlowCtrl>) -> Self {
275        Self {
276            flow,
277            inner: Mutex::new(QueInner {
278                total_pending: 0,
279                n_duplicates: 0,
280                draining: false,
281                poll_wakers: Vec::new(),
282                subs: HashMap::new(),
283                quota: 0,
284                size: event_que_size(),
285                replace_threshold: events_per_que(),
286            }),
287            wake: Notify::new(),
288        }
289    }
290
291    fn lock(&self) -> std::sync::MutexGuard<'_, QueInner> {
292        self.inner
293            .lock()
294            .unwrap_or_else(std::sync::PoisonError::into_inner)
295    }
296
297    /// Wake every reader parked on this queue — the `Notify` waiters that
298    /// [`Self::next`] suspends on AND the [`Self::poll_next`] wakers held in
299    /// [`QueInner::poll_wakers`].
300    ///
301    /// SINGLE OWNER of reader wakeup: every transition that can make a
302    /// previously-parked read Ready (a post, a producer/reader teardown, an
303    /// EVENTS_ON release) MUST come through here and MUST NOT call
304    /// `wake.notify_waiters()` directly, so the two wake mechanisms cannot
305    /// diverge — a site that woke only the `Notify` would strand a
306    /// `poll_next` consumer forever (and on the RTEMS exec backend a task
307    /// whose waker is held nowhere live is dropped outright).
308    fn wake_readers(&self) {
309        let wakers = std::mem::take(&mut self.lock().poll_wakers);
310        self.wake.notify_waiters();
311        for waker in wakers {
312            waker.wake();
313        }
314    }
315
316    /// C `db_queue_event_log` (`dbEvent.c:776-868`).
317    fn post(&self, sid: u32, event: MonitorEvent) -> PostOutcome {
318        let outcome = {
319            let mut q = self.lock();
320            let flow_on = self.flow.is_on();
321            let ring_space = q.ring_space();
322            let size = q.size;
323            let threshold = q.replace_threshold;
324            let Some(sub) = q.subs.get_mut(&sid) else {
325                return PostOutcome::Closed;
326            };
327            if sub.reader_gone {
328                return PostOutcome::Closed;
329            }
330            let npend = sub.events.len();
331            if npend > 0 && (flow_on || ring_space <= threshold) {
332                // C: `db_delete_field_log(*pLastLog); *pLastLog = pLog;` — the
333                // ring does not grow and the earlier distinct entries stay put.
334                let last = sub
335                    .events
336                    .back_mut()
337                    .expect("npend > 0 ⇒ this monitor has a last log");
338                let displaced = last.mask;
339                *last = event;
340                // The displaced VALUE is gone (C frees it); its event class is
341                // kept — see the module's documented deviations.
342                last.mask |= displaced;
343                sub.nreplace += 1;
344                PostOutcome::Replaced
345            } else {
346                sub.events.push_back(event);
347                // C: an entry appended while the monitor already had one queued
348                // is a duplicate — the queue-level count the EVENTS_OFF gate
349                // reads (`dbEvent.c:837-839`).
350                if npend > 0 {
351                    q.n_duplicates += 1;
352                }
353                q.total_pending += 1;
354                debug_assert!(
355                    q.total_pending < size,
356                    "the quota reservation must keep the ring from filling"
357                );
358                PostOutcome::Appended {
359                    first_event: ring_space == size,
360                }
361            }
362        };
363        if outcome != PostOutcome::Closed {
364            self.wake_readers();
365        }
366        outcome
367    }
368
369    /// C `event_read` (`dbEvent.c:932-1014`), reader half.
370    async fn next(&self, sid: u32) -> Option<MonitorEvent> {
371        loop {
372            // Arm the wakeup BEFORE inspecting the queue: `notify_waiters()`
373            // stores no permit, so a post or an EVENTS_ON landing between the
374            // check and the await must find this waiter already registered.
375            let wake = self.wake.notified();
376            tokio::pin!(wake);
377            wake.as_mut().enable();
378            {
379                let mut q = self.lock();
380                let flow_on = self.flow.is_on();
381                let sub = q.subs.get(&sid)?;
382                let has_entry = !sub.events.is_empty();
383                let producer_gone = sub.producer_gone;
384                if has_entry {
385                    if q.may_drain(flow_on) {
386                        q.draining = true;
387                        return q.remove_front(sid);
388                    }
389                    // Suspended: C's event_read returns without delivering.
390                } else if producer_gone {
391                    return None;
392                }
393            }
394            wake.await;
395        }
396    }
397
398    /// Poll-based [`Self::next`]: the same gate and the same delivery, but
399    /// instead of suspending on the queue's `Notify` it registers `cx`'s
400    /// waker in [`QueInner::poll_wakers`] and returns [`Poll::Pending`].
401    ///
402    /// This is what lets ONE task await MANY subscriptions — the QSRV group
403    /// drain polls each of its member readers in turn and parks once, its
404    /// waker held by every queue it polled. Registration happens under the
405    /// queue lock and every Ready-making mutation wakes through
406    /// [`Self::wake_readers`] under the same lock, so a post landing between
407    /// the check and the `Pending` return cannot be lost.
408    ///
409    /// [`Poll::Ready`]`(None)` matches [`Self::next`]'s `None`: the
410    /// subscription is detached, or its producer is gone and the queue
411    /// drained. An entry withheld by EVENTS_OFF parks exactly where
412    /// [`Self::next`] suspends (`flowCtrlMode && nDuplicates == 0`, no drain
413    /// pass in flight) and is released by the same [`Self::wake_readers`]
414    /// call `flow_ctrl_off` makes.
415    fn poll_next(
416        &self,
417        sid: u32,
418        cx: &mut std::task::Context<'_>,
419    ) -> std::task::Poll<Option<MonitorEvent>> {
420        use std::task::Poll;
421        let mut q = self.lock();
422        let flow_on = self.flow.is_on();
423        let Some(sub) = q.subs.get(&sid) else {
424            return Poll::Ready(None);
425        };
426        let has_entry = !sub.events.is_empty();
427        let producer_gone = sub.producer_gone;
428        if has_entry {
429            if q.may_drain(flow_on) {
430                q.draining = true;
431                return Poll::Ready(q.remove_front(sid));
432            }
433            // Suspended by EVENTS_OFF: park, C's event_read returns without
434            // delivering.
435        } else if producer_gone {
436            return Poll::Ready(None);
437        }
438        if !q.poll_wakers.iter().any(|w| w.will_wake(cx.waker())) {
439            q.poll_wakers.push(cx.waker().clone());
440        }
441        Poll::Pending
442    }
443
444    /// Non-blocking [`Self::next`]: same gate, no suspension.
445    fn try_next(&self, sid: u32) -> Result<MonitorEvent, TryRecvError> {
446        let mut q = self.lock();
447        let flow_on = self.flow.is_on();
448        let Some(sub) = q.subs.get(&sid) else {
449            return Err(TryRecvError::Disconnected);
450        };
451        if sub.events.is_empty() {
452            return Err(if sub.producer_gone {
453                TryRecvError::Disconnected
454            } else {
455                TryRecvError::Empty
456            });
457        }
458        if !q.may_drain(flow_on) {
459            // Suspended by EVENTS_OFF — nothing is deliverable to this monitor.
460            return Err(TryRecvError::Empty);
461        }
462        q.draining = true;
463        q.remove_front(sid).ok_or(TryRecvError::Empty)
464    }
465
466    /// Is this subscription's reader gone (C: the monitor was cancelled)?
467    /// Producer rows are reaped on this, replacing `mpsc::Sender::is_closed`.
468    fn reader_gone(&self, sid: u32) -> bool {
469        self.lock().subs.get(&sid).is_none_or(|s| s.reader_gone)
470    }
471
472    /// The producer row for `sid` is gone: no more posts, but what is already
473    /// queued is still delivered.
474    fn close_producer(&self, sid: u32) {
475        {
476            let mut q = self.lock();
477            let Some(sub) = q.subs.get_mut(&sid) else {
478                return;
479            };
480            sub.producer_gone = true;
481            if sub.reader_gone {
482                q.detach(sid);
483            }
484        }
485        self.wake_readers();
486    }
487
488    /// The reader for `sid` is gone: its queued entries leave the ring through
489    /// the same accounting every other removal uses.
490    fn close_reader(&self, sid: u32) {
491        {
492            let mut q = self.lock();
493            let Some(sub) = q.subs.get_mut(&sid) else {
494                return;
495            };
496            sub.reader_gone = true;
497            q.detach(sid);
498        }
499        self.wake_readers();
500    }
501
502    /// C `nDuplicates` — entries queued beyond the first for their monitor,
503    /// across every subscription on this queue.
504    pub fn n_duplicates(&self) -> usize {
505        self.lock().n_duplicates
506    }
507
508    /// C `nreplace` for one monitor — posts that overwrote its last entry.
509    pub fn nreplace(&self, sid: u32) -> u64 {
510        self.lock().subs.get(&sid).map_or(0, |s| s.nreplace)
511    }
512
513    /// C `npend` for one monitor — entries queued and not yet delivered.
514    pub fn npend(&self, sid: u32) -> usize {
515        self.lock().subs.get(&sid).map_or(0, |s| s.events.len())
516    }
517
518    /// C `quota` — ring entries reserved by the monitors attached here.
519    pub fn quota(&self) -> usize {
520        self.lock().quota
521    }
522}
523
524/// C `event_user` (`dbEvent.c:84-105`) — one per CA circuit. Owns the EVENTS_OFF
525/// flag and the chain of queues subscriptions attach to.
526pub struct EventUser {
527    flow: Arc<FlowCtrl>,
528    /// C `firstque` + `nextque`: a subscription attaches to the first queue with
529    /// spare quota, and a new queue is chained when none has
530    /// (`dbEvent.c:446-469`). This — not the client — is the sharing granularity
531    /// of `nDuplicates`.
532    ques: Mutex<Vec<Arc<EvQue>>>,
533}
534
535impl Default for EventUser {
536    fn default() -> Self {
537        Self::new()
538    }
539}
540
541impl EventUser {
542    /// C `db_init_events`.
543    pub fn new() -> Self {
544        let flow = Arc::new(FlowCtrl::default());
545        Self {
546            ques: Mutex::new(vec![Arc::new(EvQue::new(flow.clone()))]),
547            flow,
548        }
549    }
550
551    /// C `db_add_event`'s queue-selection loop (`dbEvent.c:446-469`): walk the
552    /// chain for the first queue whose `quota` still admits a monitor, and chain
553    /// a fresh one only when none does.
554    ///
555    /// The sharing this produces is not an implementation detail — it is where
556    /// `nDuplicates` lives (R8-23). A duplicate queued for any monitor on the
557    /// queue releases the EVENTS_OFF drain of every monitor on it, which
558    /// per-subscription rings cannot express.
559    fn attach_que(&self, sid: u32) -> Arc<EvQue> {
560        let mut ques = self
561            .ques
562            .lock()
563            .unwrap_or_else(std::sync::PoisonError::into_inner);
564        for que in ques.iter() {
565            if que.lock().try_attach(sid) {
566                return que.clone();
567            }
568        }
569        let que = Arc::new(EvQue::new(self.flow.clone()));
570        let attached = que.lock().try_attach(sid);
571        debug_assert!(attached, "a fresh queue must admit its first monitor");
572        ques.push(que.clone());
573        que
574    }
575
576    /// C `db_event_flow_ctrl_mode_on` — EVENTS_OFF. Posts now replace each
577    /// monitor's last queued entry in place, and a reader suspends once its
578    /// queue holds no duplicates.
579    pub fn flow_ctrl_on(&self) {
580        self.flow.on.store(true, Ordering::Release);
581    }
582
583    /// C `db_event_flow_ctrl_mode_off` — EVENTS_ON. Releases every suspended
584    /// reader on this circuit; C posts `ppendsem` for the same reason.
585    pub fn flow_ctrl_off(&self) {
586        self.flow.on.store(false, Ordering::Release);
587        let ques = self
588            .ques
589            .lock()
590            .unwrap_or_else(std::sync::PoisonError::into_inner);
591        for que in ques.iter() {
592            que.wake_readers();
593        }
594    }
595
596    pub fn is_flow_ctrl_on(&self) -> bool {
597        self.flow.is_on()
598    }
599}
600
601/// Why a non-blocking take found nothing. Mirrors `mpsc::error::TryRecvError` so
602/// consumers of the channel this replaced read unchanged.
603#[derive(Debug, Clone, Copy, PartialEq, Eq)]
604pub enum TryRecvError {
605    /// Nothing deliverable to this monitor right now — either its queue is empty
606    /// or EVENTS_OFF is suspending the drain.
607    Empty,
608    /// The producer row is gone and everything queued has been delivered.
609    Disconnected,
610}
611
612impl std::fmt::Display for TryRecvError {
613    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
614        match self {
615            Self::Empty => write!(f, "no monitor event available"),
616            Self::Disconnected => write!(f, "monitor producer gone"),
617        }
618    }
619}
620
621impl std::error::Error for TryRecvError {}
622
623/// The consumer half of one subscription — C's per-monitor view of `event_read`,
624/// and the single owner of the EVENTS_OFF drain-or-suspend decision. Both CA
625/// server monitor loops and every in-process consumer reach the gate through
626/// here, so they cannot disagree about what a pause does.
627///
628/// Dropping it cancels the subscription's queue slot (C `db_cancel_event`),
629/// releasing its ring entries and its quota.
630pub struct EventReader {
631    que: Arc<EvQue>,
632    sid: u32,
633}
634
635impl EventReader {
636    /// Await this subscription's next event, suspending exactly where C's
637    /// `event_read` does (`flowCtrlMode && nDuplicates == 0`, no drain pass in
638    /// flight). `None` = producer gone and queue drained.
639    pub async fn recv(&mut self) -> Option<MonitorEvent> {
640        self.que.next(self.sid).await
641    }
642
643    /// Non-blocking [`Self::recv`].
644    pub fn try_recv(&mut self) -> Result<MonitorEvent, TryRecvError> {
645        self.que.try_next(self.sid)
646    }
647
648    /// Poll-based [`Self::recv`]: `Poll::Ready(None)` where `recv()` returns
649    /// `None`, `Poll::Pending` with `cx`'s waker registered on the queue
650    /// where `recv()` suspends. For consumers that multiplex many
651    /// subscriptions from one task (the QSRV group drain) — the waker stays
652    /// registered until [`EvQue::wake_readers`] flushes it, so a caller that
653    /// polled several readers and parked is woken by whichever queue changes
654    /// first.
655    pub fn poll_recv(
656        &mut self,
657        cx: &mut std::task::Context<'_>,
658    ) -> std::task::Poll<Option<MonitorEvent>> {
659        self.que.poll_next(self.sid, cx)
660    }
661
662    /// The queue this subscription is attached to — a read-only handle for
663    /// diagnostics and tests (`n_duplicates`, `npend`, `nreplace`).
664    pub fn queue(&self) -> Arc<EvQue> {
665        self.que.clone()
666    }
667
668    /// C `npend` for this subscription.
669    pub fn npend(&self) -> usize {
670        self.que.npend(self.sid)
671    }
672}
673
674impl Drop for EventReader {
675    fn drop(&mut self) {
676        self.que.close_reader(self.sid);
677    }
678}
679
680/// The producer half of one subscription, held by the record / PV that posts to
681/// it. Dropping it is the end-of-stream signal a monitor gets when its channel
682/// goes away.
683pub struct EventSink {
684    que: Arc<EvQue>,
685    sid: u32,
686}
687
688impl EventSink {
689    /// C `db_queue_event_log` for this subscription: append, or replace this
690    /// monitor's last queued entry in place. The single owner of that decision.
691    pub fn post(&self, event: MonitorEvent) -> PostOutcome {
692        self.que.post(self.sid, event)
693    }
694
695    /// The reader is gone — the producer row can be reaped. Replaces
696    /// `mpsc::Sender::is_closed`.
697    pub fn is_closed(&self) -> bool {
698        self.que.reader_gone(self.sid)
699    }
700}
701
702impl Drop for EventSink {
703    fn drop(&mut self) {
704        self.que.close_producer(self.sid);
705    }
706}
707
708/// C `db_add_event`: attach `sid` to `user`'s queue chain and hand back the
709/// producer and consumer halves.
710pub fn attach(user: &EventUser, sid: u32) -> (EventSink, EventReader) {
711    let que = user.attach_que(sid);
712    (
713        EventSink {
714            que: que.clone(),
715            sid,
716        },
717        EventReader { que, sid },
718    )
719}
720
721#[cfg(test)]
722mod tests {
723    use super::*;
724    use crate::server::recgbl::EventMask;
725    use crate::server::snapshot::Snapshot;
726    use crate::types::EpicsValue;
727
728    fn ev(v: i32) -> MonitorEvent {
729        MonitorEvent {
730            snapshot: Snapshot::new(EpicsValue::Long(v), 0, 0, std::time::SystemTime::UNIX_EPOCH),
731            origin: 0,
732            mask: EventMask::VALUE,
733        }
734    }
735
736    fn val(e: &MonitorEvent) -> i32 {
737        match e.snapshot.value {
738            EpicsValue::Long(v) => v,
739            ref other => panic!("expected Long, got {other:?}"),
740        }
741    }
742
743    /// Boundary `npend == 0`: C appends (`dbEvent.c:832-852`) — there is no last
744    /// log to replace — even under flow control, and flags the ring's
745    /// empty→non-empty transition (C `firstEventFlag`).
746    #[epics_macros_rs::epics_test]
747    async fn npend_zero_appends_even_under_flow_control() {
748        let user = EventUser::new();
749        user.flow_ctrl_on();
750        let (sink, reader) = attach(&user, 1);
751        assert_eq!(
752            sink.post(ev(1)),
753            PostOutcome::Appended { first_event: true }
754        );
755        assert_eq!(reader.npend(), 1);
756        assert_eq!(
757            reader.queue().n_duplicates(),
758            0,
759            "a monitor's first entry is not a duplicate"
760        );
761    }
762
763    /// Boundary `npend > 0` with flow control ON: C replaces `*pLastLog` in
764    /// place (`dbEvent.c:812-820`). The queue does not grow and no duplicate is
765    /// created — which is exactly why the reader stays suspended.
766    #[epics_macros_rs::epics_test]
767    async fn npend_positive_under_flow_control_replaces_in_place() {
768        let user = EventUser::new();
769        user.flow_ctrl_on();
770        let (sink, mut reader) = attach(&user, 1);
771        sink.post(ev(1));
772        assert_eq!(sink.post(ev(2)), PostOutcome::Replaced);
773        assert_eq!(sink.post(ev(3)), PostOutcome::Replaced);
774        assert_eq!(reader.npend(), 1, "the queue never grew past one entry");
775        assert_eq!(reader.queue().nreplace(1), 2);
776        assert_eq!(reader.queue().n_duplicates(), 0);
777        user.flow_ctrl_off();
778        assert_eq!(
779            val(&reader.recv().await.unwrap()),
780            3,
781            "the latest value survives in the held entry"
782        );
783        assert!(matches!(reader.try_recv(), Err(TryRecvError::Empty)));
784    }
785
786    /// Boundary `npend > 0`, flow control OFF, ring space ABOVE the threshold:
787    /// C appends (`dbEvent.c:832-852`), so distinct updates each get their own
788    /// entry and each is delivered.
789    #[epics_macros_rs::epics_test]
790    async fn ring_space_above_threshold_appends_distinct_entries() {
791        let user = EventUser::new();
792        let (sink, mut reader) = attach(&user, 1);
793        for v in 1..=3 {
794            assert!(matches!(sink.post(ev(v)), PostOutcome::Appended { .. }));
795        }
796        assert_eq!(reader.npend(), 3);
797        assert_eq!(reader.queue().n_duplicates(), 2, "npend 3 ⇒ 2 duplicates");
798        let got: Vec<i32> = (0..3).map(|_| val(&reader.try_recv().unwrap())).collect();
799        assert_eq!(got, vec![1, 2, 3]);
800        assert_eq!(reader.queue().n_duplicates(), 0, "symmetric on drain");
801    }
802
803    /// R8-22 boundary — `npend > 0`, flow control OFF, ring space AT/BELOW the
804    /// threshold: C replaces ONLY the monitor's last entry (`dbEvent.c:812-820`)
805    /// and keeps every earlier distinct entry, so burst delivery is
806    /// {earlier distinct backlog…, coalesced tail}. The old primitive parked the
807    /// newest event in a side slot and the consumer then discarded the whole
808    /// backlog, delivering only the newest.
809    #[epics_macros_rs::epics_test]
810    async fn ring_space_at_threshold_replaces_only_the_last_entry() {
811        let user = EventUser::new();
812        let (sink, mut reader) = attach(&user, 1);
813        let appended = event_que_size() - events_per_que();
814        for v in 0..appended as i32 {
815            assert!(
816                matches!(sink.post(ev(v)), PostOutcome::Appended { .. }),
817                "post {v} must append while ring space is above the threshold"
818            );
819        }
820        assert_eq!(reader.npend(), appended);
821        // Ring space is now AT the threshold: every further post replaces the
822        // tail in place.
823        for v in 100..110 {
824            assert_eq!(sink.post(ev(v)), PostOutcome::Replaced);
825        }
826        assert_eq!(reader.npend(), appended, "the backlog did not grow");
827        assert_eq!(reader.queue().nreplace(1), 10);
828        let got: Vec<i32> = (0..appended)
829            .map(|_| val(&reader.try_recv().unwrap()))
830            .collect();
831        let mut want: Vec<i32> = (0..appended as i32 - 1).collect();
832        want.push(109);
833        assert_eq!(
834            got, want,
835            "earlier distinct entries survive; only the tail coalesced"
836        );
837        assert!(matches!(reader.try_recv(), Err(TryRecvError::Empty)));
838    }
839
840    /// The `nDuplicates` invariant survives the removal path that bypasses the
841    /// reader: a subscription torn down with entries still queued (C
842    /// `event_remove` per entry, `dbEvent.c:542-558`). Teardown drains through
843    /// the same accounting the reader uses, so the counters cannot drift.
844    #[epics_macros_rs::epics_test]
845    async fn detach_with_queued_entries_keeps_the_duplicate_count_symmetric() {
846        let user = EventUser::new();
847        let (sink, reader) = attach(&user, 1);
848        let que = reader.queue();
849        for v in 1..=3 {
850            sink.post(ev(v)); // npend 3 ⇒ 2 duplicates
851        }
852        assert_eq!(que.n_duplicates(), 2);
853        assert_eq!(que.npend(1), 3);
854        drop(reader);
855        assert_eq!(que.n_duplicates(), 0, "teardown removed its duplicates");
856        assert_eq!(que.npend(1), 0, "its entries left the ring");
857        // The row is gone, so the producer reaps itself on the next post.
858        assert_eq!(sink.post(ev(4)), PostOutcome::Closed);
859        assert!(sink.is_closed());
860    }
861
862    /// Producer gone with entries still queued: the reader drains them and only
863    /// then sees end-of-stream — the `mpsc` contract every consumer was written
864    /// against.
865    #[epics_macros_rs::epics_test]
866    async fn producer_drop_drains_then_ends_the_stream() {
867        let user = EventUser::new();
868        let (sink, mut reader) = attach(&user, 1);
869        sink.post(ev(1));
870        sink.post(ev(2));
871        drop(sink);
872        assert_eq!(val(&reader.recv().await.unwrap()), 1);
873        assert_eq!(val(&reader.recv().await.unwrap()), 2);
874        assert!(reader.recv().await.is_none(), "drained ⇒ end of stream");
875        assert!(matches!(reader.try_recv(), Err(TryRecvError::Disconnected)));
876    }
877
878    /// A reader suspended by EVENTS_OFF wakes on EVENTS_ON without needing a
879    /// further post (C signals `ppendsem` from `db_event_flow_ctrl_mode_off`).
880    /// A lost wake here would strand the monitor for good.
881    #[epics_macros_rs::epics_test]
882    async fn flow_ctrl_off_wakes_a_suspended_reader() {
883        let user = Arc::new(EventUser::new());
884        user.flow_ctrl_on();
885        let (sink, mut reader) = attach(&user, 1);
886        sink.post(ev(1));
887        sink.post(ev(2)); // replaces in place: still one entry, no duplicate
888        let u2 = user.clone();
889        let waker = crate::runtime::task::spawn(async move {
890            crate::runtime::task::yield_now().await;
891            u2.flow_ctrl_off();
892        });
893        let got = crate::runtime::task::timeout(std::time::Duration::from_secs(2), reader.recv())
894            .await
895            .expect("EVENTS_ON must wake the suspended reader")
896            .expect("the held entry is delivered");
897        waker.await.unwrap();
898        assert_eq!(val(&got), 2, "the held entry carries the latest value");
899    }
900
901    /// R8-23 boundary — a duplicate on a SIBLING subscription. C's `nDuplicates`
902    /// is a field of the queue (`dbEvent.c:80`), so `event_read`'s gate
903    /// (`flowCtrlMode && nDuplicates == 0`, `dbEvent.c:947`) is answered by the
904    /// queue as a whole: a duplicate queued for monitor B releases the drain of
905    /// monitor A's entry even though A has no duplicate of its own.
906    ///
907    /// The per-subscription queues this replaced evaluated the gate per monitor,
908    /// so A stayed suspended until EVENTS_ON.
909    #[epics_macros_rs::epics_test]
910    async fn duplicate_on_a_sibling_subscription_releases_the_events_off_drain() {
911        let user = EventUser::new();
912        let (sink_a, mut reader_a) = attach(&user, 1);
913        let (sink_b, _reader_b) = attach(&user, 2);
914        assert!(
915            Arc::ptr_eq(&reader_a.queue(), &_reader_b.queue()),
916            "the quota admits both monitors to one queue — C's sharing granularity"
917        );
918
919        sink_a.post(ev(1)); // A: npend 1, no duplicate of its own
920        sink_b.post(ev(10));
921        sink_b.post(ev(11)); // B: npend 2 ⇒ the queue holds one duplicate
922        assert_eq!(reader_a.queue().n_duplicates(), 1);
923
924        user.flow_ctrl_on();
925        let got = crate::runtime::task::timeout(std::time::Duration::from_secs(2), reader_a.recv())
926            .await
927            .expect("a duplicate anywhere on the queue must release the drain")
928            .expect("A's entry is delivered");
929        assert_eq!(val(&got), 1);
930    }
931
932    /// The complement of the boundary above — EVENTS_OFF with the queue holding
933    /// NO duplicate on any subscription: every reader on it suspends, and
934    /// EVENTS_ON releases them all.
935    #[epics_macros_rs::epics_test]
936    async fn flow_control_without_duplicates_suspends_every_reader_on_the_queue() {
937        let user = EventUser::new();
938        user.flow_ctrl_on();
939        let (sink_a, mut reader_a) = attach(&user, 1);
940        let (sink_b, mut reader_b) = attach(&user, 2);
941        sink_a.post(ev(1));
942        sink_b.post(ev(2));
943        assert_eq!(reader_a.queue().n_duplicates(), 0);
944        assert!(matches!(reader_a.try_recv(), Err(TryRecvError::Empty)));
945        assert!(matches!(reader_b.try_recv(), Err(TryRecvError::Empty)));
946        user.flow_ctrl_off();
947        assert_eq!(val(&reader_a.try_recv().unwrap()), 1);
948        assert_eq!(val(&reader_b.try_recv().unwrap()), 2);
949    }
950
951    /// Boundary `quota == size - EVENT_ENTRIES`: C chains a new queue rather than
952    /// overbooking the ring (`dbEvent.c:446-469`), so a circuit's monitors past
953    /// the cap share a *different* `nDuplicates`.
954    #[epics_macros_rs::epics_test]
955    async fn quota_exhaustion_chains_a_second_queue() {
956        let user = EventUser::new();
957        let cap = event_que_size() / EVENT_ENTRIES - 1;
958        let mut held = Vec::new();
959        for sid in 0..cap as u32 {
960            held.push(attach(&user, sid));
961        }
962        let first = held[0].1.queue();
963        for (_, reader) in &held {
964            assert!(Arc::ptr_eq(&reader.queue(), &first), "all within quota");
965        }
966        assert_eq!(first.quota(), event_que_size() - EVENT_ENTRIES);
967
968        let (_sink, overflow) = attach(&user, cap as u32);
969        assert!(
970            !Arc::ptr_eq(&overflow.queue(), &first),
971            "the {cap}th monitor exhausts the quota; the next one chains a queue"
972        );
973        assert_eq!(overflow.queue().quota(), EVENT_ENTRIES);
974    }
975
976    /// C releases the cancelled monitor's quota (`dbEvent.c:999-1002`), so the
977    /// freed slot is reusable — the next attach lands back on the first queue
978    /// instead of chaining forever.
979    #[epics_macros_rs::epics_test]
980    async fn detaching_a_monitor_releases_its_quota() {
981        let user = EventUser::new();
982        let cap = event_que_size() / EVENT_ENTRIES - 1;
983        let mut held: Vec<_> = (0..cap as u32).map(|sid| attach(&user, sid)).collect();
984        let first = held[0].1.queue();
985        assert_eq!(first.quota(), event_que_size() - EVENT_ENTRIES);
986        held.pop(); // cancel one monitor: sink and reader both go
987        assert_eq!(first.quota(), event_que_size() - 2 * EVENT_ENTRIES);
988        let (_sink, reader) = attach(&user, 900);
989        assert!(
990            Arc::ptr_eq(&reader.queue(), &first),
991            "the released quota must be reusable"
992        );
993    }
994
995    /// Teardown symmetry on a SHARED queue: cancelling one monitor removes only
996    /// its own duplicates from the queue-level count; its sibling's stay.
997    #[epics_macros_rs::epics_test]
998    async fn detaching_one_monitor_leaves_a_siblings_duplicates_counted() {
999        let user = EventUser::new();
1000        let (sink_a, reader_a) = attach(&user, 1);
1001        let (sink_b, reader_b) = attach(&user, 2);
1002        let que = reader_a.queue();
1003        for v in 1..=3 {
1004            sink_a.post(ev(v)); // A: npend 3 ⇒ 2 duplicates
1005        }
1006        for v in 10..=11 {
1007            sink_b.post(ev(v)); // B: npend 2 ⇒ 1 duplicate
1008        }
1009        assert_eq!(que.n_duplicates(), 3);
1010        drop(reader_a);
1011        drop(sink_a);
1012        assert_eq!(que.n_duplicates(), 1, "only A's duplicates left the ring");
1013        assert_eq!(que.npend(2), 2, "B's entries are untouched");
1014        drop(reader_b);
1015        drop(sink_b);
1016        assert_eq!(que.n_duplicates(), 0);
1017        assert_eq!(que.quota(), 0, "both monitors released their reservation");
1018    }
1019
1020    // -- poll_recv: the poll-based reader the QSRV group drain multiplexes on
1021
1022    /// A waker that counts its wakes, for driving `poll_recv` without a
1023    /// runtime. `std::task::Wake` gives the `RawWaker` plumbing for free.
1024    struct CountWaker(std::sync::atomic::AtomicUsize);
1025
1026    impl std::task::Wake for CountWaker {
1027        fn wake(self: Arc<Self>) {
1028            self.0.fetch_add(1, Ordering::SeqCst);
1029        }
1030    }
1031
1032    fn count_waker() -> (Arc<CountWaker>, std::task::Waker) {
1033        let counter = Arc::new(CountWaker(std::sync::atomic::AtomicUsize::new(0)));
1034        let waker = std::task::Waker::from(counter.clone());
1035        (counter, waker)
1036    }
1037
1038    /// Boundary empty→posted: a parked `poll_recv` registers its waker, a
1039    /// post wakes it exactly through `wake_readers`, and the re-poll drains
1040    /// the entry then parks again. Also proves the waker is NOT re-woken by
1041    /// its own drain (no self-wake loop for the group drain to spin on).
1042    #[test]
1043    fn poll_recv_parks_then_delivers_on_post() {
1044        let user = EventUser::new();
1045        let (sink, mut reader) = attach(&user, 7);
1046        let (counter, waker) = count_waker();
1047        let mut cx = std::task::Context::from_waker(&waker);
1048
1049        assert!(reader.poll_recv(&mut cx).is_pending(), "empty queue parks");
1050        assert_eq!(counter.0.load(Ordering::SeqCst), 0);
1051
1052        sink.post(ev(41));
1053        assert_eq!(
1054            counter.0.load(Ordering::SeqCst),
1055            1,
1056            "the post must flush the registered waker"
1057        );
1058        match reader.poll_recv(&mut cx) {
1059            std::task::Poll::Ready(Some(event)) => assert_eq!(val(&event), 41),
1060            other => panic!("expected the posted event, got {other:?}"),
1061        }
1062        assert!(
1063            reader.poll_recv(&mut cx).is_pending(),
1064            "drained queue parks"
1065        );
1066        // Delivering must not have woken the waker again.
1067        assert_eq!(counter.0.load(Ordering::SeqCst), 1);
1068    }
1069
1070    /// Boundary producer-gone: queued entries still drain through
1071    /// `poll_recv`, then the stream ends with `Ready(None)` — same contract
1072    /// as `recv()` — and the teardown itself wakes a parked poller.
1073    #[test]
1074    fn poll_recv_drains_backlog_then_reports_disconnect() {
1075        let user = EventUser::new();
1076        let (sink, mut reader) = attach(&user, 7);
1077        let (counter, waker) = count_waker();
1078        let mut cx = std::task::Context::from_waker(&waker);
1079
1080        sink.post(ev(1));
1081        sink.post(ev(2));
1082        match reader.poll_recv(&mut cx) {
1083            std::task::Poll::Ready(Some(event)) => assert_eq!(val(&event), 1),
1084            other => panic!("expected first entry, got {other:?}"),
1085        }
1086        match reader.poll_recv(&mut cx) {
1087            std::task::Poll::Ready(Some(event)) => assert_eq!(val(&event), 2),
1088            other => panic!("expected second entry, got {other:?}"),
1089        }
1090        assert!(reader.poll_recv(&mut cx).is_pending());
1091        let woken_before = counter.0.load(Ordering::SeqCst);
1092        drop(sink);
1093        assert!(
1094            counter.0.load(Ordering::SeqCst) > woken_before,
1095            "producer teardown must wake the parked poller"
1096        );
1097        assert!(
1098            matches!(reader.poll_recv(&mut cx), std::task::Poll::Ready(None)),
1099            "producer gone + queue drained ⇒ end of stream"
1100        );
1101    }
1102
1103    /// Boundary EVENTS_OFF: an entry withheld by flow control parks
1104    /// `poll_recv` exactly where `recv()` suspends (`flowCtrlMode &&
1105    /// nDuplicates == 0`), and EVENTS_ON releases it through the same
1106    /// `wake_readers` the `Notify` waiters get.
1107    #[test]
1108    fn poll_recv_respects_events_off_and_wakes_on_events_on() {
1109        let user = EventUser::new();
1110        let (sink, mut reader) = attach(&user, 7);
1111        let (counter, waker) = count_waker();
1112        let mut cx = std::task::Context::from_waker(&waker);
1113
1114        user.flow_ctrl_on();
1115        sink.post(ev(5)); // npend 0 → appends even under flow control
1116        assert!(
1117            reader.poll_recv(&mut cx).is_pending(),
1118            "EVENTS_OFF with no duplicates suspends the poll-based reader too"
1119        );
1120        user.flow_ctrl_off();
1121        assert_eq!(
1122            counter.0.load(Ordering::SeqCst),
1123            1,
1124            "the post preceded registration (woke nobody); EVENTS_ON must wake \
1125             the parked poller"
1126        );
1127        match reader.poll_recv(&mut cx) {
1128            std::task::Poll::Ready(Some(event)) => assert_eq!(val(&event), 5),
1129            other => panic!("expected the withheld entry after EVENTS_ON, got {other:?}"),
1130        }
1131    }
1132}