port(smarm): track HEAD efbc254 — RFC 014/015 API sync
- gen_server rename (RFC 015, 3e31606): ServerRef/ServerCtx/ServerBuilder
-> GenServerRef/GenServerCtx/GenServerBuilder across conn_actor,
conn_registry, serve, pubsub, channels::session.
- type Timer = () on the three GenServer impls (RFC 015, 57eadb5);
handle_timer/handle_idle/tick_every stay defaulted — opt-in later.
- Watcher and GenServerCtx are now generic over the server type: watcher
fields typed Watcher<Table<M>> / Watcher<Registry<P, K>>; Registry's
struct bounds strengthened to its GenServer impl bounds
(P: Encode + Decode + Send + Sync + 'static, K: SessionKey) so the
Watcher field's G: GenServer bound is satisfiable at the declaration.
- RFC 014 (a866e34) registry: register is (Name<M>, Sender<M>), self-only
— a name is a typed messaging endpoint, not a pid tag. The
introspection-only urus.server / urus.listener.{i} bindings are dropped
rather than faked with unit channels; the whereis integration test is
deleted; a proper messageable-name design is icebox'd in ROADMAP.md.
- Audit vs smarm 6c2b7e9 (queued messages dropped when Receiver drops):
pubsub's prune-on-send-failure retain still holds — send Errs once
receiver_alive is false, so a dropped rx prunes on next broadcast,
exactly what subscriber_count's doc already promised. Freeing stranded
Arc<M> broadcasts is strictly good. No change needed.
- The 7x E0283 in channels/mod.rs were cascade fallout of the generics
changes; dissolved with the port, as discovery predicted.
Suite: 90 lib + 45 integration + 2 doc, green under default, smarm-trace,
phoenix, and all-features. 3 pre-existing clippy lints (conn.rs,
parser.rs, conn_actor.rs; clippy 1.97 strictness) deferred to a follow-up
chore commit to keep this diff pure.
This commit is contained in:
@@ -378,3 +378,9 @@ reconnect cycles.
|
|||||||
- **Bench suite** — `urus-bench-spec.md` exists in the artefact store;
|
- **Bench suite** — `urus-bench-spec.md` exists in the artefact store;
|
||||||
wire it up once v0.2 lands (supervision changes the hot path not at all,
|
wire it up once v0.2 lands (supervision changes the hot path not at all,
|
||||||
but prove it).
|
but prove it).
|
||||||
|
- **Operator introspection via typed names** — the v0.2-era
|
||||||
|
`urus.server` / `urus.listener.{i}` pid tags were dropped in the
|
||||||
|
RFC 014 port (names are messageable endpoints now, self-registered
|
||||||
|
with a real `Sender<M>`). If wanted back, do it properly: register the
|
||||||
|
serve loop's shutdown/control channel under a typed `urus.server`
|
||||||
|
name instead of faking pid tags with unit channels.
|
||||||
|
|||||||
@@ -48,8 +48,8 @@
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use smarm::gen_server::{self, GenServer, ServerCtx};
|
use smarm::gen_server::{self, GenServer, GenServerCtx};
|
||||||
use smarm::{Down, ServerRef, Watcher};
|
use smarm::{Down, GenServerRef, Watcher};
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
@@ -87,7 +87,7 @@ pub trait ChannelSession<P>: Send + 'static {
|
|||||||
pub(super) struct SessionFactory<P: Encode + Decode + Send + Sync + 'static, K: SessionKey> {
|
pub(super) struct SessionFactory<P: Encode + Decode + Send + Sync + 'static, K: SessionKey> {
|
||||||
inner: Arc<dyn ChannelFactory<P>>,
|
inner: Arc<dyn ChannelFactory<P>>,
|
||||||
keyfn: fn(&str, &P) -> K,
|
keyfn: fn(&str, &P) -> K,
|
||||||
registry: ServerRef<Registry<P, K>>,
|
registry: GenServerRef<Registry<P, K>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The registry's working bounds for a session key.
|
/// The registry's working bounds for a session key.
|
||||||
@@ -140,14 +140,14 @@ pub(super) struct Join<P: Send + Sync + 'static, K> {
|
|||||||
inbound: Receiver<In<P>>,
|
inbound: Receiver<In<P>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Registry<P: Send + Sync + 'static, K> {
|
struct Registry<P: Encode + Decode + Send + Sync + 'static, K: SessionKey> {
|
||||||
factory: Arc<dyn ChannelFactory<P>>,
|
factory: Arc<dyn ChannelFactory<P>>,
|
||||||
cap: usize,
|
cap: usize,
|
||||||
ttl: Duration,
|
ttl: Duration,
|
||||||
sessions: HashMap<K, (Pid, Sender<Ctl<P>>)>,
|
sessions: HashMap<K, (Pid, Sender<Ctl<P>>)>,
|
||||||
/// Reverse index for `handle_down` — the reason `Key: Clone`.
|
/// Reverse index for `handle_down` — the reason `Key: Clone`.
|
||||||
pid_key: HashMap<Pid, K>,
|
pid_key: HashMap<Pid, K>,
|
||||||
watcher: Option<Watcher>,
|
watcher: Option<Watcher<Registry<P, K>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Encode + Decode + Send + Sync + 'static, K: SessionKey> GenServer for Registry<P, K> {
|
impl<P: Encode + Decode + Send + Sync + 'static, K: SessionKey> GenServer for Registry<P, K> {
|
||||||
@@ -155,8 +155,9 @@ impl<P: Encode + Decode + Send + Sync + 'static, K: SessionKey> GenServer for Re
|
|||||||
type Reply = ();
|
type Reply = ();
|
||||||
type Cast = Join<P, K>;
|
type Cast = Join<P, K>;
|
||||||
type Info = ();
|
type Info = ();
|
||||||
|
type Timer = ();
|
||||||
|
|
||||||
fn init(&mut self, ctx: &ServerCtx) {
|
fn init(&mut self, ctx: &GenServerCtx<Self>) {
|
||||||
self.watcher = Some(ctx.watcher());
|
self.watcher = Some(ctx.watcher());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -19,7 +19,7 @@ use crate::net::OwnedFd;
|
|||||||
use crate::parser::{self, ParseError};
|
use crate::parser::{self, ParseError};
|
||||||
use crate::plug::Pipeline;
|
use crate::plug::Pipeline;
|
||||||
|
|
||||||
use smarm::ServerRef;
|
use smarm::GenServerRef;
|
||||||
|
|
||||||
use std::io::{self, ErrorKind};
|
use std::io::{self, ErrorKind};
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
@@ -92,7 +92,7 @@ pub fn run_connection(
|
|||||||
fd: OwnedFd,
|
fd: OwnedFd,
|
||||||
pipeline: Pipeline,
|
pipeline: Pipeline,
|
||||||
limits: ConnLimits,
|
limits: ConnLimits,
|
||||||
registry: ServerRef<ConnRegistry>,
|
registry: GenServerRef<ConnRegistry>,
|
||||||
) {
|
) {
|
||||||
// The OwnedFd cleans up via Drop on any exit path (panic, error, or
|
// The OwnedFd cleans up via Drop on any exit path (panic, error, or
|
||||||
// normal close). No explicit close calls below.
|
// normal close). No explicit close calls below.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
//! (roadmap v0.4+), which is why it exists as its own module rather than
|
//! (roadmap v0.4+), which is why it exists as its own module rather than
|
||||||
//! being inlined into `serve`.
|
//! being inlined into `serve`.
|
||||||
|
|
||||||
use smarm::{GenServer, Pid, ServerBuilder, ServerRef};
|
use smarm::{GenServer, Pid, GenServerBuilder, GenServerRef};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ impl GenServer for ConnRegistry {
|
|||||||
type Reply = Reply;
|
type Reply = Reply;
|
||||||
type Cast = Cast;
|
type Cast = Cast;
|
||||||
type Info = ();
|
type Info = ();
|
||||||
|
type Timer = ();
|
||||||
|
|
||||||
fn handle_call(&mut self, request: Call) -> Reply {
|
fn handle_call(&mut self, request: Call) -> Reply {
|
||||||
match request {
|
match request {
|
||||||
@@ -137,8 +138,8 @@ impl GenServer for ConnRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start() -> ServerRef<ConnRegistry> {
|
pub fn start() -> GenServerRef<ConnRegistry> {
|
||||||
ServerBuilder::new(ConnRegistry::default()).start()
|
GenServerBuilder::new(ConnRegistry::default()).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -149,13 +150,13 @@ pub fn start() -> ServerRef<ConnRegistry> {
|
|||||||
/// unwind, and panic unwind alike; the cast is infallible from the
|
/// unwind, and panic unwind alike; the cast is infallible from the
|
||||||
/// guard's perspective (a dead registry just returns an ignored Err).
|
/// guard's perspective (a dead registry just returns an ignored Err).
|
||||||
pub struct DeregisterGuard {
|
pub struct DeregisterGuard {
|
||||||
registry: ServerRef<ConnRegistry>,
|
registry: GenServerRef<ConnRegistry>,
|
||||||
pid: Pid,
|
pid: Pid,
|
||||||
make: fn(Pid) -> Cast,
|
make: fn(Pid) -> Cast,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeregisterGuard {
|
impl DeregisterGuard {
|
||||||
pub fn new(registry: ServerRef<ConnRegistry>, pid: Pid, make: fn(Pid) -> Cast) -> Self {
|
pub fn new(registry: GenServerRef<ConnRegistry>, pid: Pid, make: fn(Pid) -> Cast) -> Self {
|
||||||
Self { registry, pid, make }
|
Self { registry, pid, make }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -58,7 +58,7 @@
|
|||||||
//! # Why there is no `register(name)` helper (yet)
|
//! # Why there is no `register(name)` helper (yet)
|
||||||
//!
|
//!
|
||||||
//! smarm's registry maps `name → Pid`, but a `Pid` cannot be turned back
|
//! smarm's registry maps `name → Pid`, but a `Pid` cannot be turned back
|
||||||
//! into a `ServerRef` (the ref *is* the inbox sender). A useful named
|
//! into a `GenServerRef` (the ref *is* the inbox sender). A useful named
|
||||||
//! lookup therefore needs either smarm support (registry-held senders)
|
//! lookup therefore needs either smarm support (registry-held senders)
|
||||||
//! or a process-global type-erased map here — both against the grain of
|
//! or a process-global type-erased map here — both against the grain of
|
||||||
//! the ratified design. Deferred; pass the handle.
|
//! the ratified design. Deferred; pass the handle.
|
||||||
@@ -67,8 +67,8 @@ use std::collections::{HashMap, HashSet};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use smarm::gen_server::{self, GenServer, ServerCtx};
|
use smarm::gen_server::{self, GenServer, GenServerCtx};
|
||||||
use smarm::{channel, Down, Pid, Receiver, Sender, ServerRef, Watcher};
|
use smarm::{channel, Down, Pid, Receiver, Sender, GenServerRef, Watcher};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Public handle
|
// Public handle
|
||||||
@@ -92,7 +92,7 @@ impl std::error::Error for PubSubDown {}
|
|||||||
/// Payloads are broadcast as `Arc<M>`: one allocation per broadcast, not
|
/// Payloads are broadcast as `Arc<M>`: one allocation per broadcast, not
|
||||||
/// per subscriber.
|
/// per subscriber.
|
||||||
pub struct PubSub<M: Send + Sync + 'static> {
|
pub struct PubSub<M: Send + Sync + 'static> {
|
||||||
server: ServerRef<Table<M>>,
|
server: GenServerRef<Table<M>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Send + Sync + 'static> Clone for PubSub<M> {
|
impl<M: Send + Sync + 'static> Clone for PubSub<M> {
|
||||||
@@ -226,7 +226,7 @@ struct Table<M: Send + Sync + 'static> {
|
|||||||
/// retires the entry so a reused-slot pid (fresh generation) gets a
|
/// retires the entry so a reused-slot pid (fresh generation) gets a
|
||||||
/// fresh monitor.
|
/// fresh monitor.
|
||||||
monitored: HashSet<Pid>,
|
monitored: HashSet<Pid>,
|
||||||
watcher: Option<Watcher>,
|
watcher: Option<Watcher<Table<M>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Send + Sync + 'static> Table<M> {
|
impl<M: Send + Sync + 'static> Table<M> {
|
||||||
@@ -240,8 +240,9 @@ impl<M: Send + Sync + 'static> GenServer for Table<M> {
|
|||||||
type Reply = Reply;
|
type Reply = Reply;
|
||||||
type Cast = Cast<M>;
|
type Cast = Cast<M>;
|
||||||
type Info = ();
|
type Info = ();
|
||||||
|
type Timer = ();
|
||||||
|
|
||||||
fn init(&mut self, ctx: &ServerCtx) {
|
fn init(&mut self, ctx: &GenServerCtx<Self>) {
|
||||||
self.watcher = Some(ctx.watcher());
|
self.watcher = Some(ctx.watcher());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-17
@@ -17,7 +17,7 @@ use crate::conn_registry::{self, Call, Cast, ConnRegistry, Reply};
|
|||||||
use crate::net::{accept_nonblocking, bind_and_listen, OwnedFd};
|
use crate::net::{accept_nonblocking, bind_and_listen, OwnedFd};
|
||||||
use crate::plug::Pipeline;
|
use crate::plug::Pipeline;
|
||||||
|
|
||||||
use smarm::{ChildSpec, OneForOne, Restart, ServerRef, Strategy};
|
use smarm::{ChildSpec, OneForOne, Restart, GenServerRef, Strategy};
|
||||||
|
|
||||||
use std::io::{self, ErrorKind};
|
use std::io::{self, ErrorKind};
|
||||||
use std::net::{SocketAddr, ToSocketAddrs};
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
@@ -125,7 +125,7 @@ fn listener_loop(
|
|||||||
listener: Arc<OwnedFd>,
|
listener: Arc<OwnedFd>,
|
||||||
pipeline: Pipeline,
|
pipeline: Pipeline,
|
||||||
limits: ConnLimits,
|
limits: ConnLimits,
|
||||||
registry: ServerRef<ConnRegistry>,
|
registry: GenServerRef<ConnRegistry>,
|
||||||
shutdown: Arc<AtomicBool>,
|
shutdown: Arc<AtomicBool>,
|
||||||
) {
|
) {
|
||||||
let fd = listener.as_raw();
|
let fd = listener.as_raw();
|
||||||
@@ -327,15 +327,6 @@ pub fn serve_with_shutdown(
|
|||||||
let sf = shutdown_flag.clone();
|
let sf = shutdown_flag.clone();
|
||||||
sup = sup.child(ChildSpec::new(Restart::Transient, move || {
|
sup = sup.child(ChildSpec::new(Restart::Transient, move || {
|
||||||
println!("urus: listener {} starting", i);
|
println!("urus: listener {} starting", i);
|
||||||
// Named for whereis-style introspection. On a restart the
|
|
||||||
// old binding points at a dead pid; smarm's registry
|
|
||||||
// evicts stale bindings lazily, so re-registering the
|
|
||||||
// same name is fine. Ignore the result — a registry
|
|
||||||
// hiccup must not take the listener down.
|
|
||||||
let _ = smarm::register(
|
|
||||||
format!("urus.listener.{i}"),
|
|
||||||
smarm::self_pid(),
|
|
||||||
);
|
|
||||||
listener_loop(lfd.clone(), p.clone(), limits, r.clone(), sf.clone());
|
listener_loop(lfd.clone(), p.clone(), limits, r.clone(), sf.clone());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -343,11 +334,13 @@ pub fn serve_with_shutdown(
|
|||||||
// faster than that trips the cap and tears the pool down — loud
|
// faster than that trips the cap and tears the pool down — loud
|
||||||
// failure over a zombie server.
|
// failure over a zombie server.
|
||||||
let sup_h = smarm::spawn(move || sup.run());
|
let sup_h = smarm::spawn(move || sup.run());
|
||||||
// Register via the JoinHandle's pid rather than inside the
|
// The old `urus.server` / `urus.listener.{i}` name registrations
|
||||||
// closure: the binding exists before the supervisor body runs a
|
// are gone with smarm's RFC 014 registry rework: `register` is now
|
||||||
// single instruction, so an early `whereis("urus.server")` can't
|
// `(Name<M>, Sender<M>)`, self-only — a name is a typed messaging
|
||||||
// race a None. Result ignored for the same reason as listeners.
|
// endpoint, not a pid tag. urus's bindings were introspection-only
|
||||||
let _ = smarm::register("urus.server", sup_h.pid());
|
// with no channel behind them, so they were dropped rather than
|
||||||
|
// faked with a unit channel. A real messageable `urus.server`
|
||||||
|
// name is in the icebox (ROADMAP.md).
|
||||||
|
|
||||||
// Block until told to shut down. We poll `try_recv` + `sleep`
|
// Block until told to shut down. We poll `try_recv` + `sleep`
|
||||||
// rather than parking in `recv`: a smarm `Sender::send` from a
|
// rather than parking in `recv`: a smarm `Sender::send` from a
|
||||||
@@ -399,7 +392,7 @@ pub fn serve_with_shutdown(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Our ServerRef drops here. The registry's inbox closes once
|
// 5. Our GenServerRef drops here. The registry's inbox closes once
|
||||||
// the last conn's clone drops with it, and the runtime winds
|
// the last conn's clone drops with it, and the runtime winds
|
||||||
// down when the last actor exits.
|
// down when the last actor exits.
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-18
@@ -89,24 +89,9 @@ fn hello_world() {
|
|||||||
assert_eq!(http_body(&resp), b"hello urus");
|
assert_eq!(http_body(&resp), b"hello urus");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// `server_and_listeners_are_registered` was deleted with the RFC 014 port:
|
||||||
fn server_and_listeners_are_registered() {
|
// urus no longer binds `urus.server` / `urus.listener.{i}` names (see the
|
||||||
// `whereis` must run inside the runtime (the test itself is a foreign
|
// note in serve.rs and the icebox entry in ROADMAP.md).
|
||||||
// OS thread with no runtime in its TLS), so probe from a handler —
|
|
||||||
// connection actors live in the runtime by construction.
|
|
||||||
let pipe = Pipeline::new().plug(
|
|
||||||
Router::new().get("/whereis", |c: Conn, _n: Next| {
|
|
||||||
let ok = smarm::whereis("urus.server").is_some()
|
|
||||||
&& smarm::whereis("urus.listener.0").is_some()
|
|
||||||
&& smarm::whereis("urus.listener.1").is_some(); // pool of 2
|
|
||||||
c.put_status(200).put_body(if ok { "registered" } else { "missing" })
|
|
||||||
})
|
|
||||||
);
|
|
||||||
let port = spawn_server(pipe);
|
|
||||||
let resp = send_request(port, b"GET /whereis HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n");
|
|
||||||
assert_eq!(http_status(&resp), 200);
|
|
||||||
assert_eq!(http_body(&resp), b"registered");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn echo_body() {
|
fn echo_body() {
|
||||||
|
|||||||
Reference in New Issue
Block a user