Skip to main content

taskvisor/subscribers/
subscriber.rs

1//! Defines the application callback boundary for runtime events.
2//!
3//! [`Subscribe`] implementations enter a supervisor through
4//! [`SupervisorBuilder::with_subscribers`](crate::SupervisorBuilder::with_subscribers).
5//! Each implementation gets its own bounded queue and serial callback lane after runtime startup.
6//!
7//! Ordinary runtime events pass through the shared bus and the subscriber queue.
8//! After a full lane catches up, Taskvisor delivers its coalesced overflow summary directly when
9//! the lane remains active.
10//! Subscribers are for observation, not runtime state or reliable task results.
11
12use std::num::NonZeroUsize;
13
14use crate::events::Event;
15
16const DEFAULT_QUEUE_CAPACITY: NonZeroUsize = NonZeroUsize::new(1024).unwrap();
17
18/// Callback-worker choice for a subscriber's serial lane.
19///
20/// This enum is non-exhaustive; include a wildcard arm when matching it.
21#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
22#[non_exhaustive]
23pub enum SubscriberExecution {
24    /// One fixed worker shared by all subscriber lanes using this mode.
25    ///
26    /// This is the default.
27    /// Callbacks should stay short because one blocked shared callback delays every other shared
28    /// lane in the same supervisor.
29    #[default]
30    Shared,
31
32    /// One fixed worker for this subscriber lane only.
33    ///
34    /// Use this for callbacks that may block and must not delay other subscriber lanes.
35    /// Starting a dedicated lane creates one additional OS worker thread.
36    /// Keep short callbacks on [`Shared`](Self::Shared); dedicated execution provides isolation,
37    /// not a general throughput guarantee.
38    Dedicated,
39}
40
41/// Synchronous observer for best-effort [`Event`] values.
42///
43/// Each subscriber has one serial lane.
44/// By default, all lanes use one fixed shared worker.
45/// [`SubscriberExecution::Dedicated`] gives one lane its own worker.
46/// Events delivered to [`on_event`](Self::on_event) keep FIFO order for that subscriber.
47/// Shutdown or a failed callback lane can still discard queued events.
48/// Dedicated lanes may run concurrently with the shared worker and with each other.
49/// These callbacks do not use Tokio async workers or its blocking pool.
50///
51/// Keep shared callbacks short.
52/// Dedicated execution isolates a callback that may block, but that lane can still fill its own
53/// queue and outlive the shutdown deadline.
54/// Copy the needed fields into an application-owned channel when handling requires async I/O.
55/// The borrowed event is valid only for the callback.
56///
57/// A full queue drops the incoming event only for that subscriber.
58/// Taskvisor counts dropped ordinary events and delivers one direct
59/// [`SubscriberOverflow`](crate::EventKind::SubscriberOverflow) summary after the lane catches up.
60/// Dropping an internal diagnostic, or panicking while handling one, does not generate another diagnostic.
61///
62/// Taskvisor catches an unwinding panic from an ordinary event callback and tries to publish
63/// a [`SubscriberPanicked`](crate::EventKind::SubscriberPanicked) event.
64/// A `panic = "abort"` build exits instead.
65///
66/// During shutdown, all subscriber lanes share one drain timeout.
67/// Queued events are dropped at the deadline.
68/// A callback already running cannot be aborted and may continue on its worker thread after shutdown returns.
69/// Taskvisor does not join callback workers or wait for their thread-local destructors.
70///
71/// # Examples
72///
73/// ```rust,no_run
74/// use std::num::NonZeroUsize;
75/// use std::sync::Arc;
76/// use taskvisor::{Event, EventKind, Subscribe, Supervisor, SupervisorConfig};
77///
78/// struct Metrics;
79///
80/// impl Subscribe for Metrics {
81///     fn on_event(&self, event: &Event) {
82///         if event.kind == EventKind::AttemptFailed {
83///             // Update an in-memory counter.
84///         }
85///     }
86///
87///     fn name(&self) -> &str {
88///         "metrics"
89///     }
90///
91///     fn queue_capacity(&self) -> NonZeroUsize {
92///         NonZeroUsize::new(2048).unwrap()
93///     }
94/// }
95///
96/// let subscribers: Vec<Arc<dyn Subscribe>> = vec![Arc::new(Metrics)];
97/// let supervisor = Supervisor::builder(SupervisorConfig::default())
98///     .with_subscribers(subscribers)
99///     .build();
100/// // Start it with `Supervisor::run`, `run_until`, or `serve`.
101/// ```
102pub trait Subscribe: Send + Sync + 'static {
103    /// Serial callback for one delivered event.
104    ///
105    /// Taskvisor calls this method on the subscriber's selected callback worker.
106    /// Calls never run in the event publisher or on a Tokio worker.
107    fn on_event(&self, event: &Event);
108
109    /// Callback-worker choice for this subscriber's serial lane.
110    ///
111    /// Supervisor construction reads and stores this value once.
112    /// The default is [`SubscriberExecution::Shared`].
113    fn execution(&self) -> SubscriberExecution {
114        SubscriberExecution::Shared
115    }
116
117    /// Stable name for subscriber diagnostics.
118    ///
119    /// Supervisor construction reads and stores this value once.
120    /// Choose a stable, recognizable name for logs and alerts.
121    /// The default is the fully qualified [`type_name`](std::any::type_name).
122    fn name(&self) -> &str {
123        std::any::type_name::<Self>()
124    }
125
126    /// Maximum queued events for this subscriber.
127    ///
128    /// Supervisor construction reads this value once.
129    /// Values above Taskvisor's structural async-capacity limit make
130    /// [`SupervisorBuilder::try_build`](crate::SupervisorBuilder::try_build) return
131    /// [`BuildError::CapacityTooLarge`](crate::BuildError::CapacityTooLarge).
132    /// A full queue drops the new event for this subscriber.
133    /// Increase capacity for short bursts.
134    /// A larger queue does not make a slow callback faster.
135    ///
136    /// The default capacity is `1024`.
137    fn queue_capacity(&self) -> NonZeroUsize {
138        DEFAULT_QUEUE_CAPACITY
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    struct DefaultCapacity;
147
148    impl Subscribe for DefaultCapacity {
149        fn on_event(&self, _event: &Event) {}
150    }
151
152    #[test]
153    fn subscriber_defaults_use_shared_execution_type_name_and_capacity_1024() {
154        assert_eq!(DefaultCapacity.execution(), SubscriberExecution::Shared);
155        assert_eq!(
156            DefaultCapacity.name(),
157            std::any::type_name::<DefaultCapacity>()
158        );
159        assert_eq!(DefaultCapacity.queue_capacity().get(), 1024);
160    }
161}