mirror of
https://github.com/kvcache-ai/AgentENV.git
synced 2026-11-05 01:17:36 +00:00
[PR #446] feat(ublk): restart and crash-recover the ublk daemon without losing devices #442
Labels
No labels
bug
documentation
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/AgentENV#442
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/kvcache-ai/AgentENV/pull/446
Author: @yingdi-shan
Created: 10/28/2026
Status: 🔄 Open
Base:
main← Head:feat/ublk-daemon-recovery📝 Commits (3)
418a896feat(ublk): add user recovery primitivesaafbc56feat(ublk-daemon): recover devices across daemon restarts9548067feat(ublk): respawn and recover the ublk daemon from the client📊 Changes
17 files changed (+2975 additions, -109 deletions)
View changed files
📝
Makefile(+2 -1)📝
config/default.toml(+6 -0)📝
docs/src/configuration/reference.md(+2 -0)📝
docs/src/internals/architecture.md(+11 -0)📝
src/cfg.rs(+7 -0)📝
src/sandbox/ublk/device.rs(+4 -0)📝
storage/ublk-daemon/src/client.rs(+663 -54)📝
storage/ublk-daemon/src/lib.rs(+2 -1)📝
storage/ublk-daemon/src/main.rs(+38 -6)📝
storage/ublk-daemon/src/protocol.rs(+94 -0)📝
storage/ublk-daemon/src/server.rs(+502 -18)➕
storage/ublk-daemon/tests/recovery_test.rs(+1043 -0)📝
storage/ublk-daemon/tests/ublk_daemon_test.rs(+2 -0)📝
storage/ublk/src/ctrl.rs(+153 -0)📝
storage/ublk/src/dev.rs(+108 -29)📝
storage/ublk/src/ublk_caps.rs(+13 -0)➕
tests/ublk_recovery_integration.rs(+325 -0)📄 Description
What
Let the ublk daemon restart, deliberately or after a crash, without taking running sandboxes down. Guests see a short I/O stall instead of I/O errors, lose no acknowledged writes, and keep the same
dev_idand/dev/ublkbN.Three commits, best reviewed in order:
feat(ublk): add user recovery primitives:UBLK_F_USER_RECOVERY | UBLK_F_USER_RECOVERY_REISSUEon the ctrl builder, an owner tag inublksrv_flags,START_USER_RECOVERY/END_USER_RECOVERY/GET_PARAMS, andUVMUblkDevBuilder::recover()/UVMUblkDev::resume().feat(ublk-daemon): recover devices across daemon restarts: daemonDetach(flush images, exit without deleting devices) andRecover { devices }(reattach eachDeviceSpecinto the map that tracked it, then sweep orphaned devices), plus[ublk] recovery_enabled.feat(ublk): respawn and recover the ublk daemon from the client:UblkDaemonClientkeeps an in-memory registry of handed-over devices,restart(), automatic replacement when the daemon dies, and stress tests with real sandboxes.Why
Today any ublk daemon exit kills every sandbox on the node: devices are created without recovery flags, so the kernel removes them, and the client marks the daemon dead for good. That blocks both crash tolerance and the online node upgrades proposed in #440.
Related issue
Refs #440. This implements the ublk handover part (§4) plus crash recovery; it does not close the issue.
Scope and non-goals
In scope: whole-daemon restart, both deliberate (
UblkDaemonClient::restart(), internal) and after a crash (automatic replacement), for raw, pool-exclusive, and pool-shared devices.Not in scope:
restart()is internal and exercised by tests.DeviceSpecin the per-sandboxRuntimeRecord(#440 §2) is the follow-up that adds server crash consistency.QUIESCEhandover; this stops all devices once for a whole-daemon restart.Design and behavior changes
DeviceSpec { dev_id, image_config, global_config, role }is the only record of what to recover. It is updated inside the RPC that hands a device over or releases it, under a call gate (tokio::sync::RwLock): RPCs hold it shared, replacing the daemon holds it exclusively, so a restart never snapshots a registry that misses a device.Detach(abort pack recordings,sync()every image, exit without STOP/DEL). Closing/dev/ublkcNquiesces the devices and the kernel requeues in-flight I/O. Then spawn a new daemon andRecoverthe registry.restart(). If no replacement starts, the client force-deletes every registered device so guests get EIO rather than hanging.[ublk] recovery_concurrencyoverrides it), and each device resumes as soon as its own recovery finishes. A device that cannot be recovered (for example one created without recovery flags) is deleted and reported infailed. A device that does not carry this instance's owner tag is reported infailedbut never touched, so a stale entry whose id the kernel reused cannot delete another instance's device.ublksrv_pidon exit, so the pid is useless), are not live, and were not recovered. Examples are idle pool devices and creates whose response never reached the client. Only devices that existed when the daemon started are candidates, so devices it is creating are never swept, and a graceful shutdown waits for the sweep.kill -9only because the LSMTgroup_commit_sizestays 0. This is documented indocs/src/internals/architecture.md.Compatibility and operations
Detach,Recover, andRecovered; the client and daemon ship together.[ublk] recovery_concurrency, which sets how many devices a replacement daemon recovers at once; unset or0means one per CPU. New[ublk] recovery_enabled, defaulttrue. Recovery is used only when the kernel supportsUSER_RECOVERY_REISSUE(Linux 6.0+), which is detected at runtime. With recovery enabled, I/O to a device whose daemon is gone queues instead of failing until the daemon is replaced or the client gives up and deletes the device.Validation
make fmtmake clippymake test-unitmake -C services test(required whenservices/changes)maketargetCommands and results (Linux 6.17, KVM, 16 cores, 31 GiB):
Skipped checks and reasons:
make -C services test: noservices/changes.make test-agent-integrationandmake test-ublk. The new tests in those targets were run directly with the capability runner.Risks and reviewer notes
storage/ublk-daemon/src/client.rs: the call gate,RegistryUpdate,spawn_watchdog's generation check,restart(),respawn_and_recover_once(), andfail_permanently().handle_recover,recover_device(role placement), andsweep_orphaned_devicesinstorage/ublk-daemon/src/server.rs.ublk_recovery_integrationis a separate test binary because it kills the process-wide daemon. It is added tomake test-agent-integration.Checklist
🤖 Generated with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.