establishment: the mechanics — reply capabilities, helloExchange, lineage routing
P0 of docs/establishment-planes-plan.md; no behavior changes yet, nothing sets the new flag or sends a hello capability. - service.run gains a reply-capability out-slot (replyWithCapability), the registry idiom init already uses, lifted into the harness; null stays the untouched common path. Subscribers gains claimArrival() so a provider handler can keep a turn capability through the same flag the reserved subscribe uses. - Hello wire struct: the padding byte becomes wants_channel — old callers wire-compatibly say 0, and the manager nominates a reply capability ONLY when asked, because a capability sent to a caller that never reads one is a leaked slot in that caller's table. - driver.helloExchange: one handshake can hand a serving endpoint up and receive the device's provider channel down (usb-storage will need both at once). No channel in the reply is retryable, never a verdict. - The manager stores each instance's serving endpoint on its Driver entry, routes consumer hellos by lineage (child -> reporter -> endpoint), replaces on re-hello, and closes the stale handle on death - the 32-slot table is the bound that makes forgetting this boot-fatal.
This commit is contained in:
@@ -192,6 +192,14 @@ pub fn Subscribers(comptime Protocol: type, comptime Context: type) type {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// A provider's own handler kept this turn's capability (stored it
|
||||
/// somewhere with a lifetime beyond the turn) — the same claim the
|
||||
/// reserved `subscribe` makes for its slot table. Composes with it:
|
||||
/// one flag, one `take()`, whoever claims first wins the turn.
|
||||
pub fn claimArrival() void {
|
||||
claimed = true;
|
||||
}
|
||||
|
||||
/// Answer one received packet, with the reserved `subscribe` and
|
||||
/// `unsubscribe` verbs already wired — a provider that leaves those two
|
||||
/// handlers null (every provider should) gets the harness's. The turn's
|
||||
@@ -296,6 +304,22 @@ pub fn Subscribers(comptime Protocol: type, comptime Context: type) type {
|
||||
/// the clean exit the supervisor reads as `ExitReason.exited`.
|
||||
/// `maximum_message` sizes the receive and reply buffers (a service passes its
|
||||
/// protocol's message maximum).
|
||||
/// The capability the current turn's handler nominates to ride out with its
|
||||
/// reply — init's registry idiom (`pending_capability`), lifted into the
|
||||
/// harness so any service can answer an establishment request with a channel
|
||||
/// (communication.md "Establishment: two planes"). Consumed by the loop at the
|
||||
/// very next `replyWait`, which is the reply this turn owes; null is the
|
||||
/// untouched common path. The kernel shares the endpoint as a refcounted copy,
|
||||
/// so the nominating service keeps its own handle.
|
||||
var pending_reply_capability: ?ipc.Handle = null;
|
||||
|
||||
/// Called from inside an `on_message` handler: send `handle` with this turn's
|
||||
/// reply. One capability per turn — the last nomination wins, matching the
|
||||
/// transport (a reply carries at most one).
|
||||
pub fn replyWithCapability(handle: ipc.Handle) void {
|
||||
pending_reply_capability = handle;
|
||||
}
|
||||
|
||||
pub fn run(comptime maximum_message: usize, callbacks: Callbacks) void {
|
||||
const endpoint = ipc.createIpcEndpoint() orelse return;
|
||||
if (callbacks.service) |name| {
|
||||
@@ -313,7 +337,12 @@ pub fn run(comptime maximum_message: usize, callbacks: Callbacks) void {
|
||||
var reply_len: usize = 0;
|
||||
var receive: [maximum_message]u8 = undefined;
|
||||
while (true) {
|
||||
const got = ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive, null);
|
||||
// The reply going out is the one the just-run handler wrote, so the
|
||||
// capability it nominated (if any) rides this exact replyWait and is
|
||||
// reset before the next turn can see a stale one.
|
||||
const reply_capability = pending_reply_capability;
|
||||
pending_reply_capability = null;
|
||||
const got = ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive, reply_capability);
|
||||
// Whatever capability came with this turn is the turn's, and the turn
|
||||
// closes it unless a callback claims it (`ipc.Arrival`). Structural
|
||||
// rather than a close per branch, because the branches are exactly what
|
||||
|
||||
Reference in New Issue
Block a user