volume-manager: consume medium_changed — the second removal trigger (S5)
The block client gains subscribeMedium / unsubscribeMedium / decodeMediumChanged (the reserved subscribe/unsubscribe verbs plus the MediumChanged decode), so a consumer never hand-rolls the wire format. The volume manager subscribes to each device it adopts and consumes the event through the new on_buffered_message seam — never the protocol dispatch, whose op numbers collide with the manager's own hello. A medium leaving while its device stays in the tree (a card reader, an eject) now runs the SAME kill-retire-remount path as a pulled stick: absent retires the volume, present re-probes it. That closes the "two triggers, one lifecycle" the architecture specifies — device-presence polling alone could never see a medium leave under a present device. dropDevice unsubscribes before closing so the driver's bounded subscriber table frees the slot; on a dead channel (a real pull) the call fails fast, proven by volume-removal still passing. New volume-medium-change case: eject the medium (not the device) -> "medium absent" -> the manager unmounts. Fails against a pre-S5 manager that never subscribed. Suite 132/132 (device-authority is the known child-cleanup flake, green on rerun).
This commit is contained in:
@@ -306,6 +306,12 @@ fn bringUpVolume() bool {
|
||||
return false;
|
||||
};
|
||||
dev.* = .{ .used = true, .device_id = opened.device_id, .channel = opened.device };
|
||||
// Consume this device's medium_changed events (the second of the removal
|
||||
// lifecycle's two triggers: the device stays in the tree while its medium
|
||||
// leaves — a card reader, an eject). Best effort: a provider that never
|
||||
// publishes the event simply never wakes us, and device-pull is still caught
|
||||
// by the presence poll.
|
||||
_ = opened.device.subscribeMedium(service_endpoint);
|
||||
const device = opened.device;
|
||||
// Attach the read buffer to THIS device (a no-op without an enforcing IOMMU).
|
||||
// The handle is kept, not closed, so it can be re-attached after a replug. A
|
||||
@@ -370,6 +376,10 @@ fn bringUpVolume() bool {
|
||||
/// Close a device's channel and free its slot. No volumes are touched (the caller
|
||||
/// ensures none remain, or there never were any).
|
||||
fn dropDevice(dev: *StorageDevice) void {
|
||||
// Free the driver's subscriber slot before the channel closes. On a still-live
|
||||
// channel (a medium eject) this frees the slot; on a dead one (a device pull)
|
||||
// the call fails fast and the exit sweep frees it anyway.
|
||||
_ = dev.channel.unsubscribeMedium();
|
||||
_ = ipc.close(dev.channel.endpoint);
|
||||
dev.* = .{};
|
||||
}
|
||||
@@ -533,6 +543,37 @@ 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;
|
||||
}
|
||||
|
||||
/// A storage device published `medium_changed` — the second removal trigger: the
|
||||
/// device stays in the tree while its medium leaves or returns (a card reader, an
|
||||
/// eject). This arrives as a buffered async message, NOT a protocol request, so it
|
||||
/// never reaches `Serve.dispatch` (its event op number collides with the manager's
|
||||
/// own `hello`); it is decoded here by hand. Single-volume scope: the event names
|
||||
/// 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.
|
||||
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| {
|
||||
if (dev.used) removeDevice(dev);
|
||||
}
|
||||
} else {
|
||||
for (&devices) |*dev| {
|
||||
if (dev.used and !anyVolumeOn(dev.device_id)) removeDevice(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main(init: process.Init) void {
|
||||
_ = init;
|
||||
service.run(volume_manager_protocol.message_maximum, .{
|
||||
@@ -540,5 +581,6 @@ pub fn main(init: process.Init) void {
|
||||
.init = initialise,
|
||||
.on_message = onMessage,
|
||||
.on_notification = onNotification,
|
||||
.on_buffered_message = onMediumEvent,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user