Skip to main content

frame_core/
event.rs

1//! Typed, bounded lifecycle event delivery.
2
3use std::collections::VecDeque;
4use std::num::NonZeroUsize;
5use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
6use std::sync::{Arc, Condvar, Mutex, Weak};
7use std::time::Duration;
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::capability::CapabilityDenied;
13use crate::component::ComponentId;
14
15/// An externally observable component lifecycle state.
16#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
17pub enum LifecycleState {
18    /// Definition is registered but has not started.
19    Registered,
20    /// Module loading and child liveness checks are in progress.
21    Starting,
22    /// Every declared child completed a mailbox round-trip.
23    Running,
24    /// Ordered child and supervisor drain is in progress.
25    Stopping,
26    /// All component processes have normal tombstones.
27    Stopped,
28    /// Start or supervision failed; the associated status carries the reason.
29    Failed,
30    /// Modules and registration were removed without residue.
31    Removed,
32}
33
34/// One ordered item on the component lifecycle and security-news stream.
35#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
36pub struct LifecycleEvent {
37    /// Registry-wide monotonic sequence number.
38    pub sequence: u64,
39    /// Component associated with this news item.
40    pub component_id: ComponentId,
41    /// Typed transition or capability denial payload.
42    pub kind: LifecycleEventKind,
43}
44
45/// The typed payload carried by one lifecycle stream item.
46#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
47pub enum LifecycleEventKind {
48    /// One component lifecycle transition.
49    Transition {
50        /// State before this transition, absent for initial registration.
51        from: Option<LifecycleState>,
52        /// State after this transition.
53        to: LifecycleState,
54    },
55    /// One consuming act was denied by the fresh capability check.
56    CapabilityDenied(CapabilityDenied),
57}
58
59/// A bounded subscription that reports every event it had to discard.
60pub struct LifecycleSubscription {
61    queue: Arc<SubscriberQueue>,
62}
63
64impl LifecycleSubscription {
65    /// Waits up to `timeout` for the next retained event.
66    ///
67    /// # Errors
68    ///
69    /// Returns a typed timeout, closure, or synchronization failure.
70    pub fn recv_timeout(&self, timeout: Duration) -> Result<LifecycleEvent, EventReceiveError> {
71        let guard = self
72            .queue
73            .state
74            .lock()
75            .map_err(|_| EventReceiveError::Poisoned)?;
76        let (mut guard, _wait) = self
77            .queue
78            .ready
79            .wait_timeout_while(guard, timeout, |state| {
80                state.events.is_empty() && !state.closed
81            })
82            .map_err(|_| EventReceiveError::Poisoned)?;
83        if let Some(event) = guard.events.pop_front() {
84            return Ok(event);
85        }
86        if guard.closed {
87            Err(EventReceiveError::Closed)
88        } else {
89            Err(EventReceiveError::Timeout)
90        }
91    }
92
93    /// Returns the next retained event without waiting.
94    ///
95    /// # Errors
96    ///
97    /// Returns closure, emptiness, or synchronization failures distinctly.
98    pub fn try_recv(&self) -> Result<LifecycleEvent, EventTryReceiveError> {
99        let mut state = self
100            .queue
101            .state
102            .lock()
103            .map_err(|_| EventTryReceiveError::Poisoned)?;
104        if let Some(event) = state.events.pop_front() {
105            Ok(event)
106        } else if state.closed {
107            Err(EventTryReceiveError::Closed)
108        } else {
109            Err(EventTryReceiveError::Empty)
110        }
111    }
112
113    /// Returns the cumulative number of events dropped from this subscriber.
114    #[must_use]
115    pub fn lagged_events(&self) -> usize {
116        self.queue.lagged.load(Ordering::Acquire)
117    }
118}
119
120impl Drop for LifecycleSubscription {
121    fn drop(&mut self) {
122        if let Ok(mut state) = self.queue.state.lock() {
123            state.closed = true;
124            self.queue.ready.notify_all();
125        }
126    }
127}
128
129/// Failure while waiting for an event.
130#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
131pub enum EventReceiveError {
132    /// No event arrived before the caller's deadline.
133    #[error("lifecycle event receive timed out")]
134    Timeout,
135    /// The subscription was closed.
136    #[error("lifecycle event subscription is closed")]
137    Closed,
138    /// Subscriber synchronization was poisoned by a panic.
139    #[error("lifecycle event subscription synchronization is poisoned")]
140    Poisoned,
141}
142
143/// Failure while polling for an event.
144#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
145pub enum EventTryReceiveError {
146    /// No retained event is currently available.
147    #[error("lifecycle event subscription is empty")]
148    Empty,
149    /// The subscription was closed.
150    #[error("lifecycle event subscription is closed")]
151    Closed,
152    /// Subscriber synchronization was poisoned by a panic.
153    #[error("lifecycle event subscription synchronization is poisoned")]
154    Poisoned,
155}
156
157#[derive(Default)]
158pub(crate) struct EventHub {
159    next_sequence: AtomicU64,
160    subscribers: Mutex<Vec<Weak<SubscriberQueue>>>,
161}
162
163impl EventHub {
164    pub(crate) fn subscribe(
165        &self,
166        capacity: NonZeroUsize,
167    ) -> Result<LifecycleSubscription, EventPublishError> {
168        let queue = Arc::new(SubscriberQueue {
169            capacity: capacity.get(),
170            state: Mutex::new(QueueState::default()),
171            ready: Condvar::new(),
172            lagged: AtomicUsize::new(0),
173        });
174        self.subscribers
175            .lock()
176            .map_err(|_| EventPublishError::Poisoned)?
177            .push(Arc::downgrade(&queue));
178        Ok(LifecycleSubscription { queue })
179    }
180
181    pub(crate) fn publish_transition(
182        &self,
183        component_id: ComponentId,
184        from: Option<LifecycleState>,
185        to: LifecycleState,
186    ) -> Result<(), EventPublishError> {
187        self.publish(component_id, LifecycleEventKind::Transition { from, to })
188    }
189
190    pub(crate) fn publish_denial(&self, denial: CapabilityDenied) -> Result<(), EventPublishError> {
191        self.publish(
192            denial.component_id,
193            LifecycleEventKind::CapabilityDenied(denial),
194        )
195    }
196
197    fn publish(
198        &self,
199        component_id: ComponentId,
200        kind: LifecycleEventKind,
201    ) -> Result<(), EventPublishError> {
202        let mut subscribers = self
203            .subscribers
204            .lock()
205            .map_err(|_| EventPublishError::Poisoned)?;
206        subscribers.retain(|subscriber| subscriber.strong_count() > 0);
207        if subscribers.is_empty() {
208            return Ok(());
209        }
210        let event = LifecycleEvent {
211            sequence: self.next_sequence.fetch_add(1, Ordering::AcqRel),
212            component_id,
213            kind,
214        };
215        for subscriber in subscribers.iter().filter_map(Weak::upgrade) {
216            let mut state = subscriber
217                .state
218                .lock()
219                .map_err(|_| EventPublishError::Poisoned)?;
220            if state.events.len() == subscriber.capacity {
221                let _discarded = state.events.pop_front();
222                subscriber.lagged.fetch_add(1, Ordering::AcqRel);
223            }
224            state.events.push_back(event.clone());
225            subscriber.ready.notify_one();
226        }
227        Ok(())
228    }
229}
230
231#[derive(Debug, Error)]
232pub(crate) enum EventPublishError {
233    #[error("lifecycle event stream synchronization is poisoned")]
234    Poisoned,
235}
236
237struct SubscriberQueue {
238    capacity: usize,
239    state: Mutex<QueueState>,
240    ready: Condvar,
241    lagged: AtomicUsize,
242}
243
244#[derive(Default)]
245struct QueueState {
246    events: VecDeque<LifecycleEvent>,
247    closed: bool,
248}