feat(channels): core protocol machinery — v0.6 chunk 1
Feature 'channels' (zero new deps): wire-neutral ChannelFrame/FrameRef + Encode/Decode codec traits (one method each, whole-envelope — the codec owns the wire format end to end); Channel trait; ChannelFactory + TopicRouter + shipped PrefixRouter (exact map + head:* prefix map); ChannelSocket (reply/push/broadcast/broadcast_from/broadcast_to); ChannelHub (router + PubSub<Broadcast<P>>, in-runtime + non-static per the v0.5 pattern, hub.upgrade(conn) entry, server-side hub.broadcast). Topology as ratified: one channel actor per (socket, topic), linked to the conn actor, select(&[&inbound, &bus_rx]) with inbound at index 0; subscription pinned to the channel actor pid. Subscribe-before-ok-reply makes the join ack first-class (the v0.5 on_open race, answered structurally). Cleanup is the sender-drop chain (conn handler drops -> inbound closes -> closed arm wakes -> terminate); bus arm dropped from the select set once observed closed (closed arms stay ready forever). Heartbeat answered conn-side on topic 'phoenix'; rejoin replaces the old generation with a phx_close to the old join_ref; lazy prune of dead channel entries on first failed forward (the pubsub discipline). As-landed deviations from the ratified sketch (module docs, veto by diff): join takes &mut self (trait-object callable; construction moved to ChannelFactory which still gets the full raw topic); reply(status, payload) with the ref threaded invisibly (the sketch listed both); broadcast gained the event parameter (unencodable without it); route() returns Arc not Box. Outbound payloads encode from borrows (FrameRef) so bus fan-out never clones P; payload-less control frames (phx_close, error/heartbeat replies) encode as the codec's empty payload rather than conjuring a P. 11 runtime-backed unit tests with a toy pipe codec (core provably JSON-free). Suites: 78 w/ feature, 67 featureless, both green.
This commit is contained in:
@@ -11,8 +11,15 @@ httparse = "1.9"
|
||||
libc = "0.2"
|
||||
sha1_smol = "1"
|
||||
|
||||
# dep #4, ratified 2026-06-12: serde/serde_json behind the opt-in
|
||||
# "phoenix" feature only — the "channels" core stays dependency-free.
|
||||
serde = { version = "1", optional = true }
|
||||
serde_json = { version = "1", optional = true }
|
||||
|
||||
[features]
|
||||
smarm-trace = ["smarm/smarm-trace"]
|
||||
channels = []
|
||||
phoenix = ["channels", "dep:serde", "dep:serde_json"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
+1048
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,8 @@ pub mod serve;
|
||||
pub mod sse;
|
||||
pub mod ws;
|
||||
pub mod pubsub;
|
||||
#[cfg(feature = "channels")]
|
||||
pub mod channels;
|
||||
|
||||
// Re-exports — what most users want at the crate root.
|
||||
pub use conn::{Assigns, Body, Conn, HeaderMap, HttpVersion, Method, Params, RespBody, StreamBody};
|
||||
@@ -36,6 +38,8 @@ pub use plug::{Next, Pipeline, Plug};
|
||||
pub use router::Router;
|
||||
pub use sse::{EventSender, SseClosed};
|
||||
pub use pubsub::{PubSub, PubSubDown};
|
||||
#[cfg(feature = "channels")]
|
||||
pub use channels::{Channel, ChannelHub, ChannelSocket, PrefixRouter, Status, TopicRouter};
|
||||
pub use ws::{Message, WsClosed, WsHandler, WsSender};
|
||||
pub use serve::{
|
||||
serve, serve_with, serve_with_shutdown, shutdown_handle, Config, Handle, ShutdownSignal,
|
||||
|
||||
Reference in New Issue
Block a user