1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Process-local typed event bus.
//!
//! A pub/sub primitive where **anyone can define a new event type and
//! emit / subscribe to it** without modifying this crate. Each event
//! type gets its own `tokio::sync::broadcast` channel created on
//! demand the first time someone emits or subscribes.
//!
//! # Why typed registration instead of a fixed enum
//!
//! A closed `enum LifecycleEvent { … }` forces every new event into
//! one central definition — every crate that wants a new variant has
//! to come back and edit `claviron-runtime`. With a `LifecycleEvent`
//! trait, any crate (`claviron-tls`, `claviron-php-embed`, …) can
//! introduce its own event type locally and emit it through the bus.
//! **Coupling tends to zero**, the same goal as Orbit's typed
//! publish/subscribe model.
//!
//! # Scope
//!
//! Process-local. Each process (master, worker, standalone) has its
//! own bus. Cross-process / cross-fleet events belong to a future
//! Orbit event bus, which mirrors this shape but rides over a shared
//! transport.
//!
//! # Atomicity
//!
//! `LifecycleBus` is the **atomic primitive** for in-process pubsub —
//! analogous to `AtomicU64` in the storage tier ladder. Higher layers
//! (Orbit, etc.) build on this same shape but with broader reach.
//!
//! # Usage
//!
//! ```ignore
//! // Define an event type anywhere
//! #[derive(Clone, Debug)]
//! pub struct CertReloaded { pub server_id: i32 }
//! impl claviron_runtime::events::LifecycleEvent for CertReloaded {}
//!
//! // Emit
//! state.events().emit(CertReloaded { server_id: 5 });
//!
//! // Subscribe (typed, only this event)
//! let mut rx = state.events().subscribe::<CertReloaded>();
//! while let Ok(evt) = rx.recv().await {
//! // ...
//! }
//! ```
use ;
use Arc;
use DashMap;
use broadcast;
/// Default per-type channel capacity. Subscribers that fall more than
/// `DEFAULT_CAPACITY` events behind on a single type receive
/// `RecvError::Lagged` on the next `recv()`.
const DEFAULT_CAPACITY: usize = 64;
/// Marker trait for any value that can flow through the bus.
///
/// `Clone` is required because `broadcast` channels deliver the same
/// value to every subscriber. `Send + Sync + 'static` enables storage
/// in the type-erased registry.
/// Typed, on-demand event channel registry.
///
/// Cheap to clone — internally backed by `Arc<DashMap>` plus per-type
/// `broadcast::Sender`s. Embed in `AppState` and expose via
/// `state.events()`.
// =====================================================================
// Built-in events
//
// `claviron-runtime` only ships event types that IT emits. Crates that
// emit their own events (e.g. `claviron-supervisor` for worker drain
// signals) define them in their own module and use this bus for
// transport. This keeps the registry pattern clean — the runtime is
// opinion-less about what types exist.
// =====================================================================
/// Process-wide: `state.initiate_shutdown()` was called and the
/// shutdown token was flipped. Providers that watch
/// `state.is_shutting_down()` see this as the start of their own
/// wind-down. Subscribing to this event is the push-style equivalent
/// of polling the flag.
;