diff --git a/system/services/volume-manager/volume-manager.zig b/system/services/volume-manager/volume-manager.zig index 2235d40..fea4913 100644 --- a/system/services/volume-manager/volume-manager.zig +++ b/system/services/volume-manager/volume-manager.zig @@ -555,8 +555,6 @@ fn onNotification(badge: u64) void { } } -var last_medium_change: u32 = 0; - fn anyVolumeOn(device_id: u64) bool { for (&volumes) |*v| if (v.used and v.device_id == device_id) return true; return false; @@ -570,10 +568,18 @@ fn anyVolumeOn(device_id: u64) bool { /// no device, so `absent` retires every adopted device (its volumes unmount and /// the poll re-adopts the still-present device with its now-empty medium), and /// `present` frees any empty adopted device so the poll re-probes and remounts it. +/// +/// We act on every edge and do NOT dedup on `change_count`. The driver publishes +/// exactly once per transition, each with a unique monotonic count, so a count is +/// never legitimately re-sent within one subscription — an equality dedup could +/// only ever fire spuriously, and it did: `change_count` restarts at 0 in each +/// driver instance (usb-storage.zig), so a global "last count" carried across a +/// driver restart (S5's own crash-rebuild) mistook the fresh instance's first +/// edge for a re-delivery and dropped a real eject, wedging a mount over absent +/// media. Both branches are idempotent (a freed device stops matching `dev.used`) +/// and the poll reconciles, so reacting to each genuine edge is safe. fn onMediumEvent(payload: []const u8) void { const event = block.decodeMediumChanged(payload) orelse return; - if (event.change_count == last_medium_change) return; // a coalesced or re-delivered edge - last_medium_change = event.change_count; if (event.present == 0) { std.log.info("medium left a storage device; unmounting its volume(s)", .{}); for (&devices) |*dev| {