Skip to content

feat(esp32): make ESP builds survive low-disk hosts — pre-flight free-space check, on-demand cache reclaim, actionable ENOSPC #1750

Closed

Description

@zackees

Why

An ESP32 build on a disk-constrained machine should either work or fail with something actionable. Today it does neither: fbuild can measure free space, never checks it, and when the disk fills the user gets a raw ENOSPC out of whichever subprocess happened to write next.

Follow-up from the ESP32 orchestrator work in #1730.

What happens today (evidence)

fbuild already knows how to measure free space, and no code uses it.

  • pub fn volume_facts(path: &Path) -> std::io::Result<VolumeFacts> — crates/fbuild-core/src/platform/fs.rs:180, with VolumeFacts.available_space at :24, implemented per-OS (linux/fs.rs:138, macos/fs.rs:157 via statvfs, windows/fs.rs:291 via fs2).
  • A workspace-wide grep for volume_facts / available_space outside crates/fbuild-core/src/platform/ returns only vendored third-party crates (zccache, kernal_api, sysinfo, fs2). There is no pre-flight space check anywhere in fbuild.

ESP32 is the most disk-hungry path, and its caches are only reclaimable by hand.

So a full disk shows up as an ENOSPC from a downloader, linker or subprocess, with no statement of how much is needed, on which path, or what could be freed.

Proposal

  1. Measure the requirement. Record the peak disk delta of a cold ESP32 build (toolchain + framework libs + zccache + project build dir) and publish a documented minimum per platform. Keep the measured footprint per stage so the estimate tightens as data accumulates.
  2. Pre-flight check. Before resolving packages, call volume_facts() on every root that will be written (cache root, workspace, temp) and fail fast with an actionable error: the path, free vs required, the shortfall, and the largest reclaimable caches. On a healthy machine emit a single info line.
  3. Reclaim on demand — never silently, never mid-build. An explicit, budgeted path that evicts disposable caches in a fixed order (warm toolchain/esptool installs → framework libs cache → blob/.lnk cache → zccache LRU per its existing budget), reusing the disk-cache budgets from Two-phase disk cache (archives + installed) with LRU GC, sized budgets, and crash-safe SQLite index #20 rather than inventing new policy, and reporting bytes freed. ci(esp32): daemon background GC evicts warm esptool install mid-build — elf2image ENOENT #1341 is the cautionary tale for when this may run.
  4. Translate ENOSPC when it still happens into that same actionable error, at the downloader, linker and subprocess boundaries.
  5. Optional: fbuild doctor / --check-disk — print per-root free space and reclaimable totals without building.
  6. Worth copying: the sibling running-process crate already ships an ENOSPC delete-to-recover emergency reserve (track(platform): assess W600 FastLED build support #390). Pre-flight alone cannot cover a build that grows after the check.

Non-goals

  • No automatic eviction of warm caches during a normal build.
  • No silent degradation (dropping suites, lowering quality) to fit — if it does not fit, say so.
  • No change to download or install semantics.

Acceptance criteria

  • A cold ESP32 build with ample space logs one info line and behaves exactly as today.
  • On a constrained host the build fails before package resolution, naming the path, the shortfall, and the reclaimable bytes.
  • With reclaim enabled, the build completes after freeing at least the shortfall, and the log states what was evicted.
  • An injected ENOSPC from a downloader/linker surfaces as that same message, not a raw io error.
  • Tests: mocked-threshold unit tests; an integration test pointing the cache root at a small tmpfs asserting the fail-fast message; a test that the reclaim order is honoured and never runs mid-build; a test that a roomy build's log is unchanged apart from the info line.

Related

#20 (two-phase disk cache, LRU GC, sized budgets), #952 (framework libs core cache never hydrates), #1341 (GC evicts a warm esptool install mid-build), #1370 (toolchain download has no resume), #1539, #1730 (the ESP32 orchestrator guard this check would sit beside).

Activity

  1. zackees commented on Oct 28, 2026

    @zackees
    MemberAuthor

    All six proposal items have landed; closing with the map.

    Item Landed as
    1. Measure the requirement (per-stage peak, published minimum) #1773 4eee75f0 — fbuild_build_engine::disk_footprint, FBUILD_DISK_FOOTPRINT[_JSON]; measured cold ESP32 peaks (Xtensa ~3.9 GiB, RISC-V ~4.9 GiB) set ESP32_CACHE_BYTES = 5 GiB
    2. Pre-flight check on every root, before resolution #1767 8b6e6d83 — disk_preflight; fail-fast names path, free vs required, shortfall and the largest reclaimable caches; a roomy build logs one info line
    3. Reclaim on demand, never mid-build #1772 9bf53ed5 — fbuild cache reclaim [--needed] [--dry-run] [--cache-dir], fixed RECLAIM_ORDER, refuses to run while daemon_status.json reports operation_in_progress
    4. Translate ENOSPC at downloader/linker/subprocess #1768 3bf4c228 — fbuild_core::disk_full, same message as pre-flight
    5. fbuild doctor --check-disk #1770 fa1e3c15 — per-root free space and reclaimable totals, no build
    6. Delete-to-recover emergency reserve #1774 39a96f03 — fbuild_core::disk_reserve, 32 MiB real bytes committed pre-flight, released only on an ENOSPC signature, re-armed next build

    Acceptance-criteria status:

    • fail-fast before package resolution, naming path/shortfall/reclaimable bytes — yes (disk_preflight_tests.rs: short_root_fails_with_path_required_and_shortfall, failure_names_reclaimable_caches_largest_first).
    • roomy build unchanged apart from one info line — yes (ample_space_is_sufficient_and_prints_one_line).
    • injected ENOSPC surfaces as the pre-flight message — yes (a_tool_enospc_is_translated_and_lists_reclaimable_caches, plus the downloader tests).
    • reclaim order honoured and never mid-build — yes (reclaim_tests.rs order test, cache_reclaim_tests.rs busy-daemon refusal).
    • One deviation to note: the AC asking for an integration test "pointing the cache root at a small tmpfs" is covered by fake-free-space unit tests instead of a real small tmpfs mount — mounting a tmpfs needs root in CI, so the free-space probe is the seam that is faked. Say the word if you want a privileged tmpfs job on top.

    Non-goals were respected: no automatic eviction during a normal build, no silent degradation.

    — fbuild steward (automated)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions