feat(cluster): RFC 010 c6a — connection actor + manager subtree
Per-peer connection actor as a single select-loop plain actor owning the whole FramedConn: one select folds its command inbox and the transport's readable arm, so reads and control share one execution context — no reader thread, no read/write split. The handshake is bypassed here (c6b wires it); the actor is spawned already-established and self-registers with the manager. Manager gen_server: the peer-name -> conn-pid registry and the uniqueness source the handshake's NameTaken depends on. It monitors each connection, so the table self-heals on any exit path. Explicit supervision subtree keeps the manager up; connections are dynamic and monitored, never restarted (c7 re-dials). Transport gains an additive Conn::readable_arm -> Option<FdArm> (default None; TCP returns its fd's arm, loopback stays None). Existing c3 transport tests unchanged. Lifecycle test over localhost TCP: up reflected in the table, commanded shutdown reaps exactly one, peer EOF reaps the other.
This commit is contained in:
+55
-2
@@ -2,9 +2,62 @@
|
||||
//!
|
||||
//! c1: feature flag + optional deps. c2: the owned envelope. c3: the
|
||||
//! transport trait (control connection), framed codec, and the TCP +
|
||||
//! loopback impls. c5: the handshake state machine. Everything above them
|
||||
//! lands in later chunks.
|
||||
//! loopback impls. c5: the handshake state machine. c6: the connection
|
||||
//! [`manager`] (registry) and per-peer connection actors ([`conn`]), started
|
||||
//! as an explicit supervision subtree. Everything above them lands in later
|
||||
//! chunks.
|
||||
|
||||
pub mod conn;
|
||||
pub mod envelope;
|
||||
pub mod handshake;
|
||||
pub mod manager;
|
||||
pub mod transport;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::gen_server::{self, GenServerBuilder};
|
||||
use crate::monitor::monitor;
|
||||
use crate::scheduler::{sleep, spawn, JoinHandle};
|
||||
use crate::supervisor::{ChildSpec, OneForOne, Restart};
|
||||
|
||||
pub use conn::{spawn_established, ConnHandle};
|
||||
pub use manager::{Manager, MANAGER};
|
||||
|
||||
/// A running cluster subtree: an explicitly-started supervisor over the
|
||||
/// connection [`Manager`]. Roles will eventually mount this subtree; until the
|
||||
/// role mechanism lands it is started by hand (RFC 010 §7). Dropping the handle
|
||||
/// detaches the subtree, which keeps running for the life of the runtime.
|
||||
pub struct Cluster {
|
||||
_sup: JoinHandle,
|
||||
}
|
||||
|
||||
/// Start the cluster subtree and block until the manager is registered and
|
||||
/// ready to answer. The manager is a supervised child (restarted on crash);
|
||||
/// per-peer connection actors are dynamic and monitored by the manager rather
|
||||
/// than statically supervised — a lost connection is re-established by dialing
|
||||
/// (c7), never resurrected onto a stale socket.
|
||||
pub fn start() -> Cluster {
|
||||
let sup = spawn(|| {
|
||||
OneForOne::new()
|
||||
.child(ChildSpec::new(Restart::Permanent, manager_child))
|
||||
.run()
|
||||
});
|
||||
while gen_server::whereis_server(MANAGER).is_none() {
|
||||
sleep(Duration::from_millis(1));
|
||||
}
|
||||
Cluster { _sup: sup }
|
||||
}
|
||||
|
||||
/// The supervised manager child body. It *is* the child actor: it starts the
|
||||
/// named manager, then parks on the manager's own termination so this actor's
|
||||
/// lifetime tracks the manager's — the supervisor's restart accounting keys off
|
||||
/// this actor exiting.
|
||||
fn manager_child() {
|
||||
let m = match GenServerBuilder::new(Manager::new()).named(MANAGER).start() {
|
||||
Ok(m) => m,
|
||||
// Name still held by a not-yet-reaped prior instance: return and let
|
||||
// the supervisor retry under its restart policy.
|
||||
Err(_) => return,
|
||||
};
|
||||
let _ = monitor(m.pid()).rx.recv();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user