[PR #205] [MERGED] fix(ublk): gate block prewarm on resize support #250

Closed
opened 2026-10-29 14:21:28 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/kvcache-ai/AgentENV/pull/205
Author: @ztting01
Created: 8/22/2026
Status: ✅ Merged
Merged: 8/24/2026
Merged by: @yingdi-shan

Base: main ← Head: fix/ublk-auto-prewarm-default


📝 Commits (1)

  • abe0dde fix(ublk): gate block prewarm on resize support

📊 Changes

4 files changed (+27 additions, -17 deletions)

View changed files

📝 config/default.toml (+3 -2)
📝 docs/src/configuration/reference.md (+1 -1)
📝 storage/ublk-daemon/src/main.rs (+18 -5)
📝 storage/ublk-daemon/src/server.rs (+5 -9)

📄 Description

What

Make block-device startup prewarm capability-aware. When [pool.block].startup_prewarm is omitted, the ublk daemon now enables proactive refill only when the kernel reports UBLK_F_UPDATE_SIZE. Explicit true and false values remain authoritative.

The default configuration and reference documentation now describe the capability-based default.

Why

Without UBLK_F_UPDATE_SIZE, an idle ublk device can only be reused by a request with exactly the same virtual size.

Pool watermarks are global rather than per size, while each refill batch uses one observed request size. Proactive refill can therefore fill the shared pool with one device size and prevent other sizes from being retained. Concurrent starts can amplify this because acquisitions continue growing the refill target while asynchronous refill is still in progress.

Disabling proactive refill does not disable pooling: devices returned during pause still populate the pool naturally, allowing rootfs and memory snapshot sizes to coexist and be reused by later resumes.

This is especially relevant to AgentENV's documented Linux 6.8+ baseline because upstream dynamic ublk resize was introduced later.

Closes #203

Scope and non-goals

Included:

  • Capability-based default for [pool.block].startup_prewarm.
  • Preservation of explicit operator overrides.
  • Documentation of the new default.

Not included:

  • Changes to warm-pool watermark growth or refill policy.
  • Kernel backports or alternative ublk resize implementations.
  • Changes to the Firecracker process pool.

Design and behavior changes

The daemon preserves the raw optional startup_prewarm setting while loading configuration. After the server probes ublk features, main resolves the effective value as:

explicit startup_prewarm value, if present
otherwise UBLK_F_UPDATE_SIZE support

The finalized pool configuration and detected feature flags are then passed to enable_pool. Feature detection remains encapsulated by UblkDaemonServer; main owns configuration resolution.

On kernels without dynamic resize, omitting the setting disables proactive refill but does not disable the block-device pool: devices returned by real workloads can still remain idle and be reused by matching-size requests.

Compatibility and operations

  • Public API or generated protocol: No change.
  • Configuration or defaults: An omitted [pool.block].startup_prewarm is now capability-based instead of always true. Explicit values are unchanged.
  • Snapshot manifest, artifact layout, or storage format: No change.
  • Upgrade and rollback: On kernels without UBLK_F_UPDATE_SIZE, upgrading stops proactive block-device refill by default. Operators can retain the previous behavior with startup_prewarm = true.
  • Host requirements, permissions, ports, or dependencies: No change.

Validation

  • make fmt
  • make clippy
  • make test-unit
  • Relevant Rust integration tests
  • make -C services test (required when services/ changes)
  • Generated clients/server regenerated with the documented make target
  • Documentation updated
  • Benchmarks or performance comparison completed

Commands and results:

cargo fmt --all -- --check  # passed
git diff --check            # passed

We compared three completed runs with proactive prewarm enabled and three with it disabled on a kernel without UBLK_F_UPDATE_SIZE. Each run contained 128 task logs and approximately 3,400 timing samples. The values below are arithmetic means of the three per-run summaries.

Metric Prewarm enabled Prewarm disabled Change
Pause p95 2.802 s 2.055 s -26.6%
Pause mean 1.105 s 0.723 s -34.5%
Resume p95 3.746 s 3.101 s -17.2%
Resume mean 1.169 s 1.079 s -7.7%
Total p95 41.814 s 41.077 s -1.8%
Total mean 12.312 s 11.891 s -3.4%

With prewarm enabled, 24 concurrent sandbox starts grew the asynchronous refill target to the high watermark of 64 before the first rootfs device was released. Without dynamic resize, the pool was dominated by one device size; other sizes still required new devices, and returned devices were stopped when the pool was full.

With proactive prewarm disabled, rootfs and memory snapshot devices populated the pool naturally and could both be reused by later resumes. This reduced pause overhead while also improving resume p95 and mean latency.

The refill-target amplification observed during concurrent starts is a separate issue and is not changed by this PR.

Skipped checks and reasons:

  • Rust unit and integration tests were not run locally because the ublk daemon is Linux-only and the current development host is Windows. The behavior was validated with the Linux replay workload above.
  • Service tests and generated-code checks are not applicable to these files.

Risks and reviewer notes

The main behavioral change is limited to an omitted block-pool setting on kernels without dynamic resize. Explicit configuration provides an immediate compatibility escape hatch. Reviewers should focus on the optional-setting precedence in storage/ublk-daemon/src/main.rs and the separation between feature detection and pool activation.

Checklist

  • The PR contains one coherent change and no unrelated formatting or refactoring.
  • New behavior is covered by tests, or I explained why testing is impractical.
  • Logs and examples contain no credentials, tokens, or private registry information.
  • I did not manually edit generated code without updating its source and regenerating it.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/kvcache-ai/AgentENV/pull/205 **Author:** [@ztting01](https://github.com/ztting01) **Created:** 8/22/2026 **Status:** ✅ Merged **Merged:** 8/24/2026 **Merged by:** [@yingdi-shan](https://github.com/yingdi-shan) **Base:** `main` ← **Head:** `fix/ublk-auto-prewarm-default` --- ### 📝 Commits (1) - [`abe0dde`](https://github.com/kvcache-ai/AgentENV/commit/abe0ddec12147573a0ea930710c77b94805f06a2) fix(ublk): gate block prewarm on resize support ### 📊 Changes **4 files changed** (+27 additions, -17 deletions) <details> <summary>View changed files</summary> 📝 `config/default.toml` (+3 -2) 📝 `docs/src/configuration/reference.md` (+1 -1) 📝 `storage/ublk-daemon/src/main.rs` (+18 -5) 📝 `storage/ublk-daemon/src/server.rs` (+5 -9) </details> ### 📄 Description ## What Make block-device startup prewarm capability-aware. When `[pool.block].startup_prewarm` is omitted, the ublk daemon now enables proactive refill only when the kernel reports `UBLK_F_UPDATE_SIZE`. Explicit `true` and `false` values remain authoritative. The default configuration and reference documentation now describe the capability-based default. ## Why Without `UBLK_F_UPDATE_SIZE`, an idle ublk device can only be reused by a request with exactly the same virtual size. Pool watermarks are global rather than per size, while each refill batch uses one observed request size. Proactive refill can therefore fill the shared pool with one device size and prevent other sizes from being retained. Concurrent starts can amplify this because acquisitions continue growing the refill target while asynchronous refill is still in progress. Disabling proactive refill does not disable pooling: devices returned during pause still populate the pool naturally, allowing rootfs and memory snapshot sizes to coexist and be reused by later resumes. This is especially relevant to AgentENV's documented Linux 6.8+ baseline because upstream dynamic ublk resize was introduced later. ## Related issue Closes #203 ## Scope and non-goals Included: - Capability-based default for `[pool.block].startup_prewarm`. - Preservation of explicit operator overrides. - Documentation of the new default. Not included: - Changes to warm-pool watermark growth or refill policy. - Kernel backports or alternative ublk resize implementations. - Changes to the Firecracker process pool. ## Design and behavior changes The daemon preserves the raw optional `startup_prewarm` setting while loading configuration. After the server probes ublk features, `main` resolves the effective value as: ```text explicit startup_prewarm value, if present otherwise UBLK_F_UPDATE_SIZE support ``` The finalized pool configuration and detected feature flags are then passed to `enable_pool`. Feature detection remains encapsulated by `UblkDaemonServer`; `main` owns configuration resolution. On kernels without dynamic resize, omitting the setting disables proactive refill but does not disable the block-device pool: devices returned by real workloads can still remain idle and be reused by matching-size requests. ## Compatibility and operations - Public API or generated protocol: No change. - Configuration or defaults: An omitted `[pool.block].startup_prewarm` is now capability-based instead of always `true`. Explicit values are unchanged. - Snapshot manifest, artifact layout, or storage format: No change. - Upgrade and rollback: On kernels without `UBLK_F_UPDATE_SIZE`, upgrading stops proactive block-device refill by default. Operators can retain the previous behavior with `startup_prewarm = true`. - Host requirements, permissions, ports, or dependencies: No change. ## Validation - [ ] `make fmt` - [ ] `make clippy` - [ ] `make test-unit` - [ ] Relevant Rust integration tests - [ ] `make -C services test` (required when `services/` changes) - [ ] Generated clients/server regenerated with the documented `make` target - [x] Documentation updated - [x] Benchmarks or performance comparison completed Commands and results: ```text cargo fmt --all -- --check # passed git diff --check # passed ``` We compared three completed runs with proactive prewarm enabled and three with it disabled on a kernel without `UBLK_F_UPDATE_SIZE`. Each run contained 128 task logs and approximately 3,400 timing samples. The values below are arithmetic means of the three per-run summaries. | Metric | Prewarm enabled | Prewarm disabled | Change | |---|---:|---:|---:| | Pause p95 | 2.802 s | 2.055 s | -26.6% | | Pause mean | 1.105 s | 0.723 s | -34.5% | | Resume p95 | 3.746 s | 3.101 s | -17.2% | | Resume mean | 1.169 s | 1.079 s | -7.7% | | Total p95 | 41.814 s | 41.077 s | -1.8% | | Total mean | 12.312 s | 11.891 s | -3.4% | With prewarm enabled, 24 concurrent sandbox starts grew the asynchronous refill target to the high watermark of 64 before the first rootfs device was released. Without dynamic resize, the pool was dominated by one device size; other sizes still required new devices, and returned devices were stopped when the pool was full. With proactive prewarm disabled, rootfs and memory snapshot devices populated the pool naturally and could both be reused by later resumes. This reduced pause overhead while also improving resume p95 and mean latency. The refill-target amplification observed during concurrent starts is a separate issue and is not changed by this PR. Skipped checks and reasons: - Rust unit and integration tests were not run locally because the ublk daemon is Linux-only and the current development host is Windows. The behavior was validated with the Linux replay workload above. - Service tests and generated-code checks are not applicable to these files. ## Risks and reviewer notes The main behavioral change is limited to an omitted block-pool setting on kernels without dynamic resize. Explicit configuration provides an immediate compatibility escape hatch. Reviewers should focus on the optional-setting precedence in `storage/ublk-daemon/src/main.rs` and the separation between feature detection and pool activation. ## Checklist - [x] The PR contains one coherent change and no unrelated formatting or refactoring. - [x] New behavior is covered by tests, or I explained why testing is impractical. - [x] Logs and examples contain no credentials, tokens, or private registry information. - [x] I did not manually edit generated code without updating its source and regenerating it. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-29 14:21:28 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kvcache-ai/AgentENV#250
No description provided.