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}