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

101 lines
4.2 KiB
Markdown

---
id: LSN-0050
ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Serial Async Lanes Bound Backlog Complexity
created: 2026-07-01
tags: [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.