feat(channels): implement the single-publish fixes — S-01..S-04 (reviews 010/011; ADR-047 am. 4, ADR-052, ADR-053, ADR-022 am.)
S-01 — the marked-pub open contract (ADR-047 amendment 4): - ChannelCore::register_pub_openable + PubOpenHandler (the put path's handler returns its result for the merged single response; the wrapper awaits inline, teardown is EOF-driven, handler_task stays None); register_openable* refuses Pub-typed specs; the wrapper entry points refuse unmarked/non-Pub shapes - the reserved input key `channel_id` (the initiator-allocated id): stripped pre-validation by resolve_sink_handler (the single source of truth, P-08), handed to the wrapper via OperationContext.metadata, a missing key rejected INVALID_INPUT; adopt parity enforced (ManagerError::InvalidAdopt) and adopt failures answer channel:open_failed / adopt_failed - zero call.published on marked ops: the first publish chunk on a marked op is rejected loudly (channel:publish_chunks_rejected) in both dispatch loops; the handler is never fed - relay put-translation replaces RelayError::PubTypedOpen: hub allocates odd on the producer leg, opens the spoke op in the background (put-acks-in-result), one-direction pump, response-mirror with the producer's reserved key stripped - ChannelClient::open_pub_channel + PubOpenReply (the initiator- allocates put open capability) S-02 — channel/control implemented (ADR-052): establisher-contributed ChannelControl (Establishment::with_control), manager-owned registry, teardown removals, channel:no_control_handle capability answer. S-03 — channel/resources/subscribe implemented (ADR-053): the ResourceEnumerators registry (shared per peer), snapshot-then-changes enumerators, scan-once aggregation, the empty seed; the always-failing one-shot error stream removed. S-04 — op/register forward-through (ADR-022 amendment): announced unmarked Sub/Pub register the real streaming/sink forwarding shapes; marked announcements refused with MARKED_SPEC_UNSUPPORTED; marked specs refuse the generic registration path and register_imported (Result-returning now). Guard (review 010 action 3): tests/stub_guard.rs greps src/ for not_implemented; the four pinned stub tests are deleted and the marked-spec registration refusals are pinned by tests. Verified: cargo test (696 + guard), clippy --all-targets -D warnings, fmt --check, fuzz corpus replay.
This commit is contained in:
1 parent
df4d4d3a9e
commit
944c300c17
18 files changed
+2418
-326
No files matched your search
@@ -18,6 +18,9 @@ use tokio::sync::mpsc;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::protocol::wire::CallError;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::mux::{MuxHandle, OpenerLedger};
|
||||
use super::reassembly::{MpscRecvStream, MpscSendStream, DEFAULT_BUFFER_CAP};
|
||||
|
||||
@@ -38,6 +41,15 @@ pub enum ChannelSide {
|
||||
/// `ChannelLifecyclePolicy` (ADR-041, amended by ADR-047 §7).
|
||||
pub const DEFAULT_MAX_CHANNELS: usize = 256;
|
||||
|
||||
/// The per-channel out-of-band control callback (ADR-052): received
|
||||
/// from the open-op establisher (`Establishment::with_control`),
|
||||
/// installed by the open-op wrapper under the channel's id, routed to
|
||||
/// by `channel/control`, removed by every teardown path. The manager
|
||||
/// stays blind to what the closure does (ADR-039).
|
||||
pub type ChannelControl = Arc<
|
||||
dyn Fn(Value) -> futures::future::BoxFuture<'static, Result<Value, CallError>> + Send + Sync,
|
||||
>;
|
||||
|
||||
/// The per-channel state — the reassembly sender (the demux feeds chunk
|
||||
/// payloads into it; the handler reads them out via `MpscRecvStream`),
|
||||
/// the handler task handle, and the ALPN (for observability).
|
||||
@@ -62,6 +74,13 @@ pub enum ManagerError {
|
||||
UnknownChannel(u32),
|
||||
#[error("channel already exists: {0}")]
|
||||
ChannelExists(u32),
|
||||
#[error(
|
||||
"channel {channel_id} is outside the remote side's allocation range (expected {expected})"
|
||||
)]
|
||||
InvalidAdopt {
|
||||
channel_id: u32,
|
||||
expected: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
/// The shared state for a channels connection. `Clone` (cheap — `Arc`
|
||||
@@ -101,6 +120,10 @@ struct Inner {
|
||||
/// drains them into the new receiver. `clear_all` drops the map.
|
||||
early_arrivals: Mutex<HashMap<u32, VecDeque<Bytes>>>,
|
||||
early_arrival_count: AtomicU64,
|
||||
/// Per-channel out-of-band control callbacks (ADR-052). Installed
|
||||
/// by the open-op wrapper from the establisher's
|
||||
/// `Establishment::with_control`; removed by every teardown path.
|
||||
control_handles: Mutex<HashMap<u32, ChannelControl>>,
|
||||
}
|
||||
|
||||
/// The per-channel cap on parked early-arrival chunks (`route_payload`
|
||||
@@ -151,6 +174,7 @@ impl ChannelManager {
|
||||
dropped_unknown_chunks: AtomicU64::new(0),
|
||||
early_arrivals: Mutex::new(HashMap::new()),
|
||||
early_arrival_count: AtomicU64::new(0),
|
||||
control_handles: Mutex::new(HashMap::new()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -317,6 +341,32 @@ impl ChannelManager {
|
||||
alpn: impl Into<String>,
|
||||
handler_task: Option<JoinHandle<()>>,
|
||||
) -> Result<(MpscSendStream, MpscRecvStream), ManagerError> {
|
||||
// The adopt range is the remote side's allocation range (the
|
||||
// odd/even split, ADR-047 §5): the connect side allocates odd,
|
||||
// so the accept side adopts odd; the accept side allocates
|
||||
// even, so the connect side adopts even. Adopting inside the
|
||||
// local range would collide with this side's own monotonic
|
||||
// allocator shortly after. The duplicate check comes first —
|
||||
// adopting an id this side already holds returns
|
||||
// `ChannelExists` and must never touch the live channel (the
|
||||
// 0.8.1 regression pin).
|
||||
let expected = match self.inner.side {
|
||||
ChannelSide::Connect => "even (the accept side's allocated ids; ADR-047 §5)",
|
||||
ChannelSide::Accept => "odd (the connect side's allocated ids; ADR-047 §5)",
|
||||
};
|
||||
if self.inner.channels.lock().contains_key(&channel_id) {
|
||||
return Err(ManagerError::ChannelExists(channel_id));
|
||||
}
|
||||
let parity_ok = match self.inner.side {
|
||||
ChannelSide::Connect => channel_id.is_multiple_of(2),
|
||||
ChannelSide::Accept => !channel_id.is_multiple_of(2),
|
||||
};
|
||||
if !parity_ok {
|
||||
return Err(ManagerError::InvalidAdopt {
|
||||
channel_id,
|
||||
expected,
|
||||
});
|
||||
}
|
||||
let alpn = alpn.into();
|
||||
|
||||
let send = self
|
||||
@@ -381,6 +431,30 @@ impl ChannelManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Install the out-of-band control callback for a channel
|
||||
/// (ADR-052). Called by the open-op wrapper from the establisher's
|
||||
/// `Establishment::with_control`. Replaces an existing callback for
|
||||
/// the id (the establisher runs once per channel — replacement is
|
||||
/// not an expected path; the last call wins).
|
||||
pub fn set_control_handle(&self, channel_id: u32, control: ChannelControl) {
|
||||
self.inner
|
||||
.control_handles
|
||||
.lock()
|
||||
.insert(channel_id, control);
|
||||
}
|
||||
|
||||
/// The control callback for a channel, for routing (the
|
||||
/// `channel/control` handler). `None` when the channel's ALPN
|
||||
/// registered no control surface (the op answers
|
||||
/// `channel:no_control_handle`).
|
||||
pub fn control_handle(&self, channel_id: u32) -> Option<ChannelControl> {
|
||||
self.inner.control_handles.lock().get(&channel_id).cloned()
|
||||
}
|
||||
|
||||
fn remove_control_handle(&self, channel_id: u32) {
|
||||
self.inner.control_handles.lock().remove(&channel_id);
|
||||
}
|
||||
|
||||
/// Install channel 0 (pre-negotiated as `alk/call`, ADR-036).
|
||||
/// Channel 0 is special only in that it's pre-allocated (by
|
||||
/// `channels-call`); the `ChannelsAdapter` hands the resulting
|
||||
@@ -530,6 +604,7 @@ impl ChannelManager {
|
||||
// Dropping `state.demux_sender` signals EOF to the
|
||||
// handler's read half (REQ-CH-02).
|
||||
drop(state.demux_sender);
|
||||
self.remove_control_handle(channel_id);
|
||||
Ok(state.handler_task)
|
||||
}
|
||||
None => Err(ManagerError::UnknownChannel(channel_id)),
|
||||
@@ -550,6 +625,7 @@ impl ChannelManager {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
self.inner.control_handles.lock().clear();
|
||||
// The early-arrival buffers die with the connection too.
|
||||
self.inner.early_arrivals.lock().clear();
|
||||
// The opener ledger has the opener PeerIds.
|
||||
|
||||
Reference in new issue
Block a user