Skip to main content

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}