All checks were successful
Intrepid/Prometeu/Runtime/pipeline/pr-master This commit looks good
101 lines
4.2 KiB
Markdown
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.
|