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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
//! The [`HealthProbe`]: a cloneable, watch-backed view of the running service's lifecycle state
//! and of how many of its subscriptions are consuming.
//!
//! The standard deployment shape runs the messaging service beside another tokio task - typically
//! an HTTP server with a `/healthz` route. When the service fail-fasts, the process stays alive
//! because of that sibling task, and the probe is what lets its endpoint report "the app died,
//! stop routing traffic here" instead of a permanent 200.
use pending;
use watch;
/// The lifecycle state reported by a [`HealthProbe`].
///
/// [`RustStream::start`](super::RustStream::start) is the readiness gate (it resolves only after
/// subscriptions are open), so the probe covers the post-startup half of the lifecycle.
/// How many of the service's subscriptions are consuming, as a [`HealthProbe`] reports it.
///
/// A subscription counts once, whatever its worker count. It is active from the moment its
/// dispatch loop starts until the loop ends: on shutdown, or when its stream ends while the
/// service runs (see [`SubscriptionEnd`]). Whether a service with fewer active subscriptions than
/// it started is healthy is the service's own call.
///
/// # Examples
///
/// ```
/// # #[cfg(all(feature = "macros", feature = "memory", feature = "json"))]
/// # mod demo {
/// use ruststream::memory::prelude::*;
/// use ruststream::runtime::{HealthProbe, RustStreamError, SubscriptionCount};
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Order {
/// id: u64,
/// }
///
/// #[subscriber("orders")]
/// async fn accept(order: &Order) -> HandlerOutcome {
/// tracing::info!(order.id, "accepted");
/// HandlerOutcome::ack()
/// }
///
/// /// Healthy while the service runs and every subscription it started still reads its queue.
/// fn healthy(health: &HealthProbe) -> bool {
/// let SubscriptionCount {
/// active, started, ..
/// } = health.subscriptions();
/// health.is_running() && active == started
/// }
///
/// pub async fn run() -> Result<(), RustStreamError> {
/// let app = RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(
/// MemoryBroker::new(),
/// |b| {
/// b.include(accept);
/// },
/// );
/// let running = app.start().await?;
/// tracing::info!(healthy = healthy(&running.health()), "service started");
/// running.shutdown().await
/// }
/// # }
/// # fn main() {}
/// ```
/// What a subscription's stream ending while the service runs does, set with
/// [`RustStream::on_subscription_end`](super::RustStream::on_subscription_end).
///
/// A stream can end without a shutdown: the server cancels a consumer when its queue is deleted,
/// a broker's reconnection policy gives up, or a broker is configured not to reconnect. An end
/// during shutdown is part of the shutdown and is never reported.
///
/// # Examples
///
/// ```
/// # #[cfg(all(feature = "macros", feature = "memory", feature = "json"))]
/// # mod demo {
/// use ruststream::memory::prelude::*;
/// use ruststream::runtime::SubscriptionEnd;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Order {
/// id: u64,
/// }
///
/// #[subscriber("orders")]
/// async fn accept(order: &Order) -> HandlerOutcome {
/// tracing::info!(order.id, "accepted");
/// HandlerOutcome::ack()
/// }
///
/// /// The service is useless without its queue, so it stops when the queue stops.
/// pub fn app() -> impl App {
/// RustStream::new(AppInfo::new("orders", "0.1.0"))
/// .on_subscription_end(SubscriptionEnd::FailFast)
/// .with_broker(MemoryBroker::new(), |b| {
/// b.include(accept);
/// })
/// }
/// # }
/// # fn main() {}
/// ```
/// What the probe reads: the lifecycle state and the subscription count, one value, so a wake-up
/// covers both.
pub
/// A cheap, cloneable handle reporting the service's [`HealthState`] and how many of its
/// subscriptions are consuming.
///
/// Obtained from [`RunningApp::health`](super::RunningApp::health); backed by a
/// [`watch`](tokio::sync::watch) channel written only on lifecycle transitions and when a
/// subscription's loop starts or ends, so reading is a lock-free borrow and nothing runs on the
/// per-delivery hot path. The probe outlives [`shutdown`](super::RunningApp::shutdown) (which
/// consumes the app handle), so a sibling HTTP task keeps answering with the terminal state.
///
/// Watch semantics: observers always see the latest state; rapid transitions (`ShuttingDown`
/// immediately followed by `Stopped`) may coalesce, so poll [`state`](Self::state) for snapshots
/// and use [`changed`](Self::changed) only as a wake-up.
///
/// Dropping the [`RunningApp`](super::RunningApp) without calling `shutdown` detaches the service
/// (per the crate rule that destructors never block); a probe then keeps reporting the last
/// observed state, and [`changed`](Self::changed) resolves only if a fail-fast failure still
/// flips the state or a subscription still ends.
/// The write half the running app, its fail-fast watcher and the dispatch loops drive; probes
/// are subscribed off it, so the public surface stays read-only.
///
/// Every write uses `send_modify`, which keeps the value when no probe is subscribed yet (where
/// `send` drops it): a probe taken after a change must still observe it.
pub ;