establishment: a reporter's death reaps its subtree, and the re-report rebuilds it
P3 of docs/establishment-planes-plan.md — the restart-zombie fix. A class driver cannot observe its provider's death: an HID driver blocks on interrupt reports that will simply never come, and storage answers its callers with refusals forever. Worse, the dead generation's still-used entries made the matcher's dedupe refuse the respawn when the restarted bus re-reported — the subtree was a permanent zombie, which is the exact opposite of the restart-a-driver-live goal the driver model exists for. pruneChildrenOf now reaps: each pruned child's bound driver is killed and its entry cleared (the exit notification finds no entry, so the death is never double-counted; its device returns by the loan rule; its stored endpoint handle is closed). The re-report then spawns a fresh generation whose hellos fetch the successor's channel. The usb-report drill now asserts the subtree WORKS after the restart: the respawned storage opens its device on the NEW bus instance and reads block 0. Discrimination: against the pre-reap manager the drill fails — no reap line, no post-restart respawn (the survivors were zombies), verified by a stash run. Note the scenario boots no input service, so the HID drivers of BOTH generations exit after their input lookup times out — storage is the functional proof.
This commit is contained in:
@@ -212,16 +212,41 @@ fn addChild(parent: u64, bus_address: u64, identity: u64, device_id: u64, report
|
|||||||
/// protocol state (slots, rings) that died with the process — keeping the nodes
|
/// protocol state (slots, rings) that died with the process — keeping the nodes
|
||||||
/// would be keeping a lie. The restarted instance rediscovers and re-reports.
|
/// would be keeping a lie. The restarted instance rediscovers and re-reports.
|
||||||
/// Watchers hear the honest story: removed now, added again on rediscovery.
|
/// Watchers hear the honest story: removed now, added again on rediscovery.
|
||||||
|
///
|
||||||
|
/// **A reporter's death reaps its subtree.** The class drivers spawned for the
|
||||||
|
/// pruned children hold channels into the dead process; they cannot observe
|
||||||
|
/// the death themselves (an HID driver blocks on interrupt reports that will
|
||||||
|
/// simply never come — a silent zombie), and their still-`used` entries would
|
||||||
|
/// make the matcher's dedupe refuse the respawn when the re-report arrives.
|
||||||
|
/// So each is killed and its entry cleared: the re-report spawns a fresh
|
||||||
|
/// instance, whose hello fetches the successor's channel. That is the restart
|
||||||
|
/// story working — kill a bus driver and only its subtree blinks
|
||||||
|
/// (docs/establishment-planes-plan.md P3).
|
||||||
fn pruneChildrenOf(reporter: u32) void {
|
fn pruneChildrenOf(reporter: u32) void {
|
||||||
for (&children) |*child| {
|
for (&children) |*child| {
|
||||||
if (child.used and child.reporter == reporter) {
|
if (child.used and child.reporter == reporter) {
|
||||||
std.log.info("child removed (device {d} port {d})", .{ child.parent, child.bus_address });
|
std.log.info("child removed (device {d} port {d})", .{ child.parent, child.bus_address });
|
||||||
|
reapDriverBoundTo(child.device_id);
|
||||||
child.used = false;
|
child.used = false;
|
||||||
Serve.publish(.child_removed, 0, .{ .parent = child.parent, .bus_address = child.bus_address });
|
Serve.publish(.child_removed, 0, .{ .parent = child.parent, .bus_address = child.bus_address });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Kill the driver bound to a pruned child and clear its entry — the exit
|
||||||
|
/// notification that follows finds no entry and is ignored, so the death is
|
||||||
|
/// never double-counted, and the freed entry is what lets the re-report
|
||||||
|
/// respawn. The device it was given returns to us by the kernel's loan rule.
|
||||||
|
fn reapDriverBoundTo(device_id: u64) void {
|
||||||
|
const driver = driverEntryForDevice(device_id) orelse return;
|
||||||
|
if (driver.process_id != 0) {
|
||||||
|
std.log.info("reaping {s} (its provider died)", .{driver.name()});
|
||||||
|
_ = process.kill(driver.process_id);
|
||||||
|
}
|
||||||
|
if (driver.endpoint) |endpoint| _ = ipc.close(endpoint);
|
||||||
|
driver.* = .{};
|
||||||
|
}
|
||||||
|
|
||||||
/// How many children a driver instance has reported (the test-usb-restart
|
/// How many children a driver instance has reported (the test-usb-restart
|
||||||
/// trigger counts these).
|
/// trigger counts these).
|
||||||
fn childCountOf(reporter: u32) u32 {
|
fn childCountOf(reporter: u32) u32 {
|
||||||
|
|||||||
+16
-4
@@ -606,9 +606,17 @@ CASES = [
|
|||||||
"smp": 4,
|
"smp": 4,
|
||||||
"expect": r"DANOS-TEST-RESULT: PASS",
|
"expect": r"DANOS-TEST-RESULT: PASS",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
# M18.2: bus tree reports — the xHCI driver scans its root-hub ports and
|
# M18.2 + establishment P3: bus tree reports, and the restart REBUILDING
|
||||||
# reports both QEMU devices; the manager mirrors, prunes on the reporter's
|
# the subtree. The manager prunes on the reporter's death — reaping the
|
||||||
# death, and the respawned driver re-reports (docs/device-manager.md).
|
# class drivers bound to the pruned children, which hold channels into the
|
||||||
|
# dead process and cannot observe the death themselves — and when the
|
||||||
|
# respawned instance re-reports, the matcher spawns fresh class drivers
|
||||||
|
# whose hellos fetch the successor's channel. The tail asserts the subtree
|
||||||
|
# WORKS again: the respawned usb-storage opens its device on the NEW bus
|
||||||
|
# instance and reads block 0. (The HID "ok" lines never appear in this
|
||||||
|
# scenario — it boots no input service — so storage is the functional
|
||||||
|
# proof.) Before the reap existed, the stale entries blocked the respawn
|
||||||
|
# and the survivors were silent zombies, so this tail could not match.
|
||||||
{"name": "usb-report",
|
{"name": "usb-report",
|
||||||
"smp": 4,
|
"smp": 4,
|
||||||
"timeout": 150,
|
"timeout": 150,
|
||||||
@@ -617,9 +625,13 @@ CASES = [
|
|||||||
"expect": r"device-manager: child added[\s\S]*"
|
"expect": r"device-manager: child added[\s\S]*"
|
||||||
r"device-manager: child added[\s\S]*"
|
r"device-manager: child added[\s\S]*"
|
||||||
r"device-manager: test mode: killing the reporter[\s\S]*"
|
r"device-manager: test mode: killing the reporter[\s\S]*"
|
||||||
|
r"device-manager: reaping \S*usb-storage[\s\S]*"
|
||||||
r"device-manager: child removed[\s\S]*"
|
r"device-manager: child removed[\s\S]*"
|
||||||
r"device-manager: restarting \S*usb-xhci-bus[\s\S]*"
|
r"device-manager: restarting \S*usb-xhci-bus[\s\S]*"
|
||||||
r"device-manager: child added",
|
r"device-manager: child added[\s\S]*"
|
||||||
|
r"device-manager: delegated device \d+ to /system/drivers/usb-storage[\s\S]*"
|
||||||
|
r"usb-storage: ready[\s\S]*"
|
||||||
|
r"usb-storage: block 0 signature 0x55aa",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
# USB HID end to end: boot the full tree, enumerate the xHCI, and let the
|
# USB HID end to end: boot the full tree, enumerate the xHCI, and let the
|
||||||
# manager spawn the USB keyboard driver, which opens its device over the
|
# manager spawn the USB keyboard driver, which opens its device over the
|
||||||
|
|||||||
Reference in New Issue
Block a user