bQUARKz 5d3db4c22a
All checks were successful
Intrepid/Prometeu/Runtime/pipeline/pr-master This commit looks good
[PERF] Async Background Work Lanes for Assets and FS
2026-07-01 10:09:33 +01:00

4.2 KiB

id ticket title created tags
LSN-0050 perf-async-background-work-lanes-for-assets-and-fs Serial Async Lanes Bound Backlog Complexity 2026-07-01
runtime
asset
async
scheduler
telemetry

Context

The async asset work moved from request-owned worker creation toward a runtime-owned serial lane. The important shift is not just "use a worker thread"; it is the introduction of a third logical execution lane with explicit ownership, priority, cancellation, progress, and telemetry.

This work followed DEC-0034 and plans PLN-0123 through PLN-0128. The published model separates:

  • main runtime execution, where guest-visible state is committed;
  • render worker execution, where closed render packets are consumed;
  • async IO/decode/persistence work, where one background job is active at a time.

Key Decisions

Async Work Lane and Asset Backlog Contract

What: Asset IO/decode and compatible persistence work use a runtime-owned serial async lane. Asset requests are queued by target bank slot, not by unbounded transient request identity.

Why: A serial lane keeps the runtime observable and portable. It preserves the intended hardware mental model while avoiding a desktop-biased thread pool or one OS thread per asset request.

Trade-offs: The lane intentionally sacrifices parallel decode throughput in exchange for bounded state, deterministic ownership, simpler telemetry, and clear priority rules. If future implementations add more physical parallelism, they still need to preserve the logical serial contract where the spec requires it.

Patterns and Algorithms

The asset backlog is bounded by target identity. A request targets a concrete bank type and slot. A newer request for the same target supersedes the older pending request, and an older active result is discarded when its generation is no longer current.

Stable handles observe slots. A handle should not be treated as a worker job or thread token. The durable identity is the bank slot plus request generation: slot state says what is resident; request state says what is queued, active, ready, canceled, superseded, or failed.

Commit remains a main-lane operation. The background lane may read and decode, but publication into resident runtime state happens at predictable ownership points. This keeps VM execution, render handoff, and frame observation from racing with background mutation.

Progress and telemetry are closure-oriented. Use integer progress and update expensive aggregate telemetry when jobs close, not inside decode loops. Tests should synchronize on explicit state transitions rather than sleeps.

Priority is part of the lane contract. Memcard commit/write work can outrank ordinary asset loads, FS write/config work can be represented internally, and non-critical read/list work belongs below asset loads. Public FS semantics remain owned by the filesystem discussion; sharing the lane is not permission to decide the FS API here.

Pitfalls

Do not hide concurrency behind per-request thread::spawn. That appears simple locally but loses boundedness, priority, cancellation, and telemetry.

Do not let a handle mean "the current background job." Handles must survive empty slots, completed work, cancellation, superseding, errors, and already resident fast paths.

Do not install background results directly from the worker. Decode completion is not the same as publication. Use request generation checks before committing a result.

Do not turn operational states into traps. Queued, active, canceled, superseded, error, and backend-unavailable outcomes belong in status-first surfaces unless the caller violated the structural ABI.

Do not reopen FS public API scope while wiring internal async-lane consumers. The lane can support FS-style work without deciding request/poll semantics.

Takeaways

  • A runtime async lane is an ownership boundary, not just an implementation thread.
  • Backlog keying by target slot bounds complexity better than queue length limits exposed to the guest.
  • Stable slot handles plus request generations prevent stale async results from mutating newer state.
  • Main-lane commit keeps background IO/decode compatible with deterministic runtime publication.
  • Telemetry belongs at state transitions and job closure, not inside hot decode loops.