Signals over IPC, one-shot timers, and the service harness (M17.4)

Signals are statements delivered as coalescing notifications to the
endpoint a process nominates with signal_bind — never a hijacked stack,
never a question (liveness is the zero-length ping the harness answers).
process_signal is supervisor-or-self gated, like kill; unbound targets
accumulate a pending mask delivered on bind. timer_bind is the missing
timed wait: a one-shot deadline landing in the same replyWait as
everything else — what stop(), hello deadlines, and restart backoff are
built from. runtime.service.run folds requests, signals, and
notifications into callbacks; the VFS conversion deletes its hand-rolled
loop and gains the whole lifecycle contract. The signals scenario drives
ping, reload, terminate->exited, the timer, and the deaf-child
deadline->killed path from ring 3. docs/process-lifecycle.md increments
1-4 are now as-built.
This commit is contained in:
Daniel Samson
2026-07-12 23:53:38 +01:00
parent d8c55c6f2f
commit 650a1b1595
15 changed files with 507 additions and 30 deletions
+22 -28
View File
@@ -138,42 +138,36 @@ fn handle(message: []const u8, out: []u8, sender: u32) usize {
}
}
pub fn main() void {
const endpoint = runtime.ipc.createIpcEndpoint() orelse {
_ = runtime.system.write("vfs: no endpoint\n");
return;
};
if (!runtime.ipc.register(.vfs, endpoint)) {
_ = runtime.system.write("vfs: register failed\n");
return;
}
// First subscriber of the published exit events (docs/process-lifecycle.md):
// when a client dies holding open handles, the notification below is how the
// VFS learns to release them.
/// Startup, under the harness: subscribe to the published exit events — when a
/// client dies holding open handles, the exit notification is how the VFS learns
/// to release them (docs/process-lifecycle.md).
fn initialise(endpoint: runtime.ipc.Handle) bool {
if (!runtime.process.subscribeExits(endpoint)) {
_ = runtime.system.write("vfs: exit subscription failed\n");
}
_ = runtime.system.write("vfs: ready\n");
return true;
}
var reply_buffer: [protocol.message_maximum]u8 = undefined;
var reply_len: usize = 0;
var receive: [protocol.message_maximum]u8 = undefined;
while (true) {
const got = runtime.ipc.replyWait(endpoint, reply_buffer[0..reply_len], &receive, null);
if (got.isChildExit()) {
// A published exit event: a process died; drop whatever it held.
releaseClientHandles(got.childProcessId());
reply_len = 0;
continue;
}
if (got.isNotification()) {
reply_len = 0; // not a request; nothing owed
continue;
}
reply_len = handle(receive[0..got.len], &reply_buffer, got.senderTaskId());
/// A non-signal notification: the only kind the VFS subscribes to is exit events.
fn onNotification(badge: u64) void {
if (badge & runtime.ipc.notify_exit_bit != 0) {
releaseClientHandles(@intCast(badge & ~(runtime.ipc.notify_badge_bit | runtime.ipc.notify_exit_bit)));
}
}
pub fn main() void {
// The harness owns the loop: requests dispatch to handle(), exit events to
// onNotification(), ping and terminate are answered for free — this service
// gained the whole lifecycle contract by deleting its hand-rolled loop.
runtime.service.run(protocol.message_maximum, .{
.service = .vfs,
.init = initialise,
.on_message = handle,
.on_notification = onNotification,
});
}
pub const panic = runtime.panic;
comptime {
_ = &runtime.start._start;