mirror of
https://github.com/kvcache-ai/AgentENV.git
synced 2026-11-05 01:17:36 +00:00
[PR #431] [MERGED] fix(ublk-daemon): roll back device handoffs the client never receives #433
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#433
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/431
Author: @Alex-Wengg
Created: 10/23/2026
Status: ✅ Merged
Merged: 10/27/2026
Merged by: @yingdi-shan
Base:
main← Head:fix/ublk-daemon-undelivered-handoff📝 Commits (1)
48960c6fix(ublk-daemon): roll back device handoffs the client never receives📊 Changes
1 file changed (+121 additions, -2 deletions)
View changed files
📝
storage/ublk-daemon/src/server.rs(+121 -2)📄 Description
What
Roll back a device the ublk daemon hands to a client when the client never receives the response.
handle_connectionnow treats the three responses that hand over a device (DeviceAcquired,DeviceCreated,OverlaybdRuntimeDeviceCreated) as a handoff:ECONNRESET, so a reset means the client gave up after the response was written but before reading it.An undelivered handoff is undone the way the client would have released it: pooled devices go through
handle_release_overlaybd(one acquisition, so one shared reference), and raw devices go throughhandle_delete.Why
When an acquire outlives the client's RPC timeout, the daemon records the acquisition, fails to write the response (
Broken pipe), and returns. The device or shared reference stays active, and the response was the client's only record of it, so nothing ever releases it. Repeated timeouts accumulate leaked devices and references.Related issue
Closes #392
Scope and non-goals
OverlaybdRuntimeDeviceCreatedis left in place. It lives under the sandbox work dir, which the agent cleans up when sandbox creation fails, and the client chose that path, so removing it late from the daemon could race a retry.acquire_overlaybdreturns). That is the caller's ownership problem.Design and behavior changes
ECONNRESETon the follow-up read). Any other outcome counts as delivered, because releasing a device the client still uses is worse than leaking one.tokio::spawn), and clients drop the stream as soon ascall_innerreturns, so this is normally immediate.Compatibility and operations
AF_UNIXreset semantics, which the daemon (ublk) already requires.Validation
make fmtmake clippymake test-unitmake -C services test(required whenservices/changes)maketargetCommands and results (Ubuntu 24.04 on WSL2, kernel 6.18.33.1, Rust 1.99.1):
The new tests run
deliver_device_handoffover a realUnixStream::pair(), so they exercise the kernel's reset behavior rather than a mock. With theECONNRESETcheck disabled,dropped_unreadanddropped_mid_readfail, confirming the tests catch the written-but-unread window.Skipped checks and reasons:
make clippy(whole workspace) andmake test-unit: ran the crate-scoped equivalents above; the rest of the workspace is untouched.CONFIG_BLK_DEV_UBLK, so thehandle_release_overlaybd/handle_deleterollback calls were not exercised on a live device. They are the existing release paths, called with thedev_idthe same request just handed out. The issue's barrier-based regression test (abandon a shared acquire while another client holds the device) would need a ublk host; happy to add it if you'd like it in this PR.Risks and reviewer notes
deliver_device_handoffinstorage/ublk-daemon/src/server.rs. It relies on LinuxAF_UNIXsemantics:unix_release_socksetsECONNRESETon the peer when the closing socket still has unread data, and stream reads report that error before EOF.ECONNRESETcounts. Any other read outcome (EOF, data, other errors) is treated as delivered.UblkDaemonClient::call_inner, which drops the stream right after reading the response. A custom client that kept the connection open would park one detached task until it closes, with no effect on other requests.stop_all_devicesalready took it. That is logged and harmless.Checklist
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.