Skip to main content

netget/state/
server_handles.rs

1//! Registry of **live** server instances.
2//!
3//! # The problem this exists to solve
4//!
5//! Protocol actions are dispatched through `Server::execute_action()`, which is
6//! called on the *stateless* protocol struct from the registry — `TcpProtocol`,
7//! `WireGuardProtocol`, … — a zero-sized description of the protocol, not the
8//! server that is actually running. It has no channel, no socket and no peer table.
9//!
10//! That is fine for **sync** actions, which are answers to a network event and are
11//! carried back to the connection task that raised the event (`ActionResult::Send`
12//! and friends). It is fatal for **async** actions — the ones a user or the model
13//! can invoke at any time, with no event in flight. `list_peers`,
14//! `get_server_info`, `disconnect_client` and their kin have nowhere to read from
15//! and nothing to write to, so the only thing they can honestly return is
16//! `ActionResult::NoAction`. Several protocols advertised such actions to the model
17//! and quietly did nothing; the VPN family's were deleted as dead rather than
18//! fixed.
19//!
20//! # The shape of the fix
21//!
22//! A running server registers a **handle** — whatever type it likes, typically a
23//! struct of `mpsc::Sender`s and `Arc<RwLock<…>>` views onto its live state — and
24//! the action path looks it up by [`ServerId`](crate::state::ServerId). The
25//! registry is type-erased (`Arc<dyn Any + Send + Sync>`) so that `AppState` does
26//! not need to know any protocol's types, which would be exactly the centralised
27//! protocol registry CLAUDE.md forbids. Each protocol downcasts back to its own
28//! handle type on the way out, and a wrong type is a `None`, not a panic.
29//!
30//! Lifetime is tied to the server: [`AppState::remove_server`] drops the handle
31//! along with the server's tasks and scheduled tasks, so a handle can never outlive
32//! the thing it points at.
33//!
34//! # Wiring (the part outside `src/state/`)
35//!
36//! `src/llm/actions/` is where dispatch happens, and it needs one addition for any
37//! of this to be reachable — see `AppState::register_server_handle` for the exact
38//! signature. In outline:
39//!
40//! 1. `Server` gains an `execute_action_with_state(action, state, server_id)`
41//!    returning a boxed future, whose default implementation delegates to today's
42//!    `execute_action` so every protocol that needs nothing live is unaffected.
43//! 2. `executor::execute_actions` calls that instead of `execute_action`; it is
44//!    already `async` and already holds both `&AppState` and `Option<ServerId>`.
45//! 3. A protocol that wants live state overrides it, calls
46//!    `state.server_handle::<MyHandle>(server_id)`, and talks to its own server.
47
48use std::any::Any;
49use std::sync::Arc;
50
51/// A type-erased handle to a running server instance.
52///
53/// Produced by `Arc::new(my_handle)` in a protocol's `spawn()`, consumed by
54/// `AppState::server_handle::<MyHandle>()` on the action path.
55pub type ServerHandle = Arc<dyn Any + Send + Sync>;