liminal_server/server/embedded.rs
1//! The embedding handle: a running liminal server with no listener, whose one
2//! connection-granting surface is the in-process loopback
3//! (design `docs/design/IN-PROCESS-TRANSPORT.md` §2, §4, §9 ruling 4).
4//!
5//! **What an [`EmbeddedServer`] secures is the RECORD PATH** (hardened
6//! face-substrate draft r2 §5). A connection minted here is admitted by the
7//! same door a socket connection is admitted by: the same
8//! `try_reserve_admission` against the same slot pool, a real durable
9//! connection incarnation from the same authority, the same registry record,
10//! the same `Connect`/`ConnectAck` handshake with the same constant-time token
11//! compare, the same frame preflight, the same participant gate, and the same
12//! `apply_frame` seam. No append reaches the record except through that door,
13//! and the door does not know which mount knocked. An embedded caller with the
14//! wrong token is refused on its own loopback, and an embedded caller arriving
15//! at capacity is refused exactly as a socket connect is.
16//!
17//! **What an [`EmbeddedServer`] does NOT secure is the mount.** A co-resident
18//! caller is TRUSTED CODE: it reaches the host process's heap, its descriptors,
19//! and its store handle without ever calling this type, so the record vouches
20//! for a co-resident mount only as far as the host process itself is trusted.
21//! That is inherent to the mount, not a defect of it. Every append admitted
22//! here carries the mount fact the admitting door stamped
23//! ([`MountKind::Loopback`](crate::server::mount::MountKind::Loopback), §10)
24//! precisely because the mount is what a consumer must weigh; this type is not
25//! a sandbox and must never be read as one.
26//!
27//! **The surface is deliberately one door wide.** This handle exposes no
28//! supervisor, no services, no store, no handler, no registry, and no scheduler
29//! — the module privacy that keeps those unreachable is the structural half of
30//! the no-side-door guarantee, and a convenience accessor here would undo it as
31//! surely as a public spawn seam would. `connect_loopback` is the whole grant.
32
33use std::fmt;
34use std::sync::Arc;
35
36use crate::ServerError;
37use crate::config::types::LimitsConfig;
38use crate::server::connection::{
39 ConnectionServices, ConnectionSupervisor, LoopbackClientEnd, LoopbackDuplex,
40};
41
42/// Bytes each direction of an embedded connection's duplex may hold.
43///
44/// 256 KiB, chosen to sit in the same order as the kernel socket buffers the
45/// loopback replaces: a default `SO_SNDBUF`/`SO_RCVBUF` pair on Linux and macOS
46/// is tens to a couple of hundred kilobytes, so an embedded writer meets
47/// backpressure at roughly the point a socket writer meets it and the mount
48/// does not quietly buy itself a deeper queue than every other mount has. It is
49/// a BOUND, not a reservation — each ring is a `VecDeque` that grows toward
50/// this ceiling only under load, so an idle embedded connection costs two empty
51/// queues.
52///
53/// The value is per ring rather than shared, so a backed-up inbound direction
54/// cannot starve the server's replies out of the outbound one.
55const LOOPBACK_RING_CAPACITY_BYTES: usize = 256 * 1024;
56
57/// A running liminal server with no listener, granting in-process connections.
58///
59/// Built from the same ingredients the production stack is built from —
60/// services, an optional connection auth token, and the operational limits —
61/// and torn down on drop, so an embedded server's lifetime is its handle's.
62pub struct EmbeddedServer {
63 supervisor: ConnectionSupervisor,
64}
65
66impl fmt::Debug for EmbeddedServer {
67 /// Prints nothing about the server.
68 ///
69 /// A `Debug` that rendered the supervisor would be an accessor by another
70 /// name: it would put the runtime's contents, the admission counter, and
71 /// the registry into any log line that formatted this handle.
72 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73 formatter
74 .debug_struct("EmbeddedServer")
75 .finish_non_exhaustive()
76 }
77}
78
79impl EmbeddedServer {
80 /// Starts an embedded server over `services`, open-access and at the
81 /// default limits.
82 ///
83 /// # Errors
84 /// Returns [`ServerError`] when incarnation startup or scheduler startup
85 /// fails.
86 pub fn with_services(services: Arc<dyn ConnectionServices>) -> Result<Self, ServerError> {
87 Self::with_services_auth_and_limits(services, None, LimitsConfig::default())
88 }
89
90 /// Starts an embedded server over `services`, gated by `auth_token` when
91 /// one is configured, under `limits`.
92 ///
93 /// The three arguments are exactly the three
94 /// [`ConnectionSupervisor::with_services_auth_and_limits`] takes, because
95 /// an embedded server is a production stack with the listener removed and
96 /// nothing else: `None` for the token is the open-access server an absent
97 /// `[auth]` section produces, and `limits` carries the same
98 /// `max_connections` bound that both connection admission and the durable
99 /// incarnation stream enforce.
100 ///
101 /// # Errors
102 /// Returns [`ServerError`] when incarnation startup or scheduler startup
103 /// fails.
104 pub fn with_services_auth_and_limits(
105 services: Arc<dyn ConnectionServices>,
106 auth_token: Option<Vec<u8>>,
107 limits: LimitsConfig,
108 ) -> Result<Self, ServerError> {
109 // P0 #56 R3. `server::run` was the only production caller of
110 // `metrics::init`, and an embedder never reaches `run` — so an embedded
111 // deployment had an entirely INERT metrics surface: every recording
112 // helper guards on the uninstalled registry and silently does nothing.
113 // That is how a field estate refused 82,166 consecutive connections
114 // with nothing to scrape. `init` is idempotent, so a host that also
115 // calls `run` (or calls this twice) pays nothing.
116 crate::metrics::init();
117 Ok(Self {
118 supervisor: ConnectionSupervisor::with_services_auth_and_limits(
119 services, auth_token, limits,
120 )?,
121 })
122 }
123
124 /// Admits one in-process connection and returns the caller's end of it.
125 ///
126 /// This is the whole grant. It replaces exactly the listener's `accept()` +
127 /// `spawn_connection` pair and nothing else about admission: the returned
128 /// end is a byte stream that has not yet handshaken, so the caller still
129 /// sends `Connect` and still receives `ConnectAck` or `ConnectError` from
130 /// the same `connect_response` a socket client reaches.
131 ///
132 /// Dropping the returned end tears the connection down by the same
133 /// end-of-file a socket hangup produces, releasing its admission slot.
134 ///
135 /// # Errors
136 /// Returns [`ServerError::ConnectionLimitReached`] when the server is at
137 /// its `max_connections` bound — the identical typed refusal a socket
138 /// connect receives at capacity, surfaced as an error rather than a panic —
139 /// and other [`ServerError`] values when incarnation allocation or process
140 /// spawn fails. On every refusal the duplex is dropped whole, so no half of
141 /// a rejected connection survives.
142 pub fn connect_loopback(&self) -> Result<LoopbackClientEnd, ServerError> {
143 let (client, server) = LoopbackDuplex::bounded(LOOPBACK_RING_CAPACITY_BYTES);
144 // The handle is deliberately discarded: it is a pid plus an incarnation,
145 // and handing it back would be a second surface onto the connection the
146 // supervisor now owns. The registry record is what keeps the connection
147 // addressable, and the client end is what keeps it alive.
148 self.supervisor.spawn_loopback_connection(server)?;
149 Ok(client)
150 }
151}
152
153impl Drop for EmbeddedServer {
154 /// Stops the connection scheduler, mirroring the in-tree socket fixtures'
155 /// teardown (`SdkSocketFixture::stop`): every host record is removed while
156 /// the readiness owner is still live, then the scheduler is shut down.
157 ///
158 /// Teardown is `Drop` alone rather than a `shutdown` method plus `Drop`
159 /// because Rust drop points are already deterministic — a caller that wants
160 /// the server stopped at a particular moment drops it at that moment — and
161 /// a second spelling of the same act is a second surface to keep honest.
162 fn drop(&mut self) {
163 self.supervisor.shutdown();
164 }
165}