vantage_api_pool/resilient/observer.rs
1//! What a client tells the outside world about its traffic. The consumer
2//! (the desktop app's health registry) keys everything by the `key` given to
3//! the builder — the datasource name — and the crate never learns what a
4//! datasource is.
5
6use std::time::Duration;
7
8use super::ClientError;
9
10#[derive(Debug, Clone)]
11pub enum TransportEvent {
12 /// Once per attempt: immediately before the request is sent, or, for a
13 /// fail-fast rejection, immediately before its `Failed { ms: 0 }`.
14 Started,
15 /// One attempt got a `2xx`. `bytes` is the `Content-Length`, when sent.
16 Succeeded {
17 status: u16,
18 ms: u64,
19 bytes: Option<u64>,
20 },
21 /// One attempt failed. The last `Failed` of a call carries the error the
22 /// call returns.
23 Failed {
24 error: ClientError,
25 ms: u64,
26 },
27 /// The call's future was dropped after `Started` and before the attempt
28 /// landed: the request is abandoned, and nothing is known about how the
29 /// server would have answered.
30 Cancelled,
31 /// A retry will run after `after`; `attempt` is its 1-based number.
32 RetryScheduled {
33 after: Duration,
34 attempt: usize,
35 },
36 BreakerOpened {
37 cooldown: Duration,
38 },
39 BreakerClosed,
40 /// Reported by the caller once it has parsed a response.
41 RowsPulled {
42 n: usize,
43 },
44 /// Reported by the caller once a write was acknowledged.
45 WritePushed,
46}
47
48pub trait TransportObserver: Send + Sync {
49 /// Called synchronously on the request path, from the task that owns the
50 /// call, and — for `Started` — while that attempt's semaphore permit is
51 /// held. The contract:
52 ///
53 /// - **Do not block, await or sleep.** Copy what you need into your own
54 /// state and return. Time spent in `Started` is time the client's
55 /// parallelism budget is not doing work; time spent in any other event
56 /// delays the call that reported it.
57 /// - **Do not call back into the client** (`execute`, `execute_with`,
58 /// `breaker_state`). The call path is holding client state.
59 /// - `Started` pairs with exactly one terminal event — `Succeeded`,
60 /// `Failed` or `Cancelled` — per attempt, including for a fail-fast rejection, which
61 /// emits a synthetic `Started` followed by `Failed { ms: 0 }` whose
62 /// error has `kind_name() == "breaker_open"`. Those two never touched
63 /// the network; filter them out of request-rate figures.
64 /// - `BreakerOpened` repeats on every failed probe, with a longer
65 /// `cooldown` each time and no intervening `BreakerClosed`. Treat it as
66 /// "open until now + cooldown" — set the deadline, do not count the
67 /// events.
68 /// - `ms` covers building, sending and reading the response head. It
69 /// excludes waiting for a breaker cooldown, a rate-limit token, a
70 /// semaphore permit, a retry back-off, and the auth round trip.
71 fn on_event(&self, key: &str, event: TransportEvent);
72}