11 KiB
| id | ticket | title | status | created | accepted | ref_agenda | plans | tags | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| DEC-0034 | perf-async-background-work-lanes-for-assets-and-fs | Async Work Lane and Asset Backlog Contract | accepted | 2026-06-28 | 2026-06-28 | AGD-0008 |
|
|
DEC-0034 - Async Work Lane and Asset Backlog Contract
Status
Accepted.
Contexto
asset.load() currently creates an OS thread per non-resident asset request.
That behavior does not match the target runtime model: the machine should have
explicit execution lanes, bounded ownership, observable backpressure, and no
uncontrolled thread creation from guest-visible operations.
Render worker concurrency is already handled by separate render decisions. This decision covers the third runtime lane for asynchronous IO/decode/persistence work:
Lane 1: VM, firmware, SystemOS, logical frame
Lane 2: render worker / asynchronous rasterization
Lane 3: async work lane for asset IO/decode and game persistence work
The concrete first consumer is asset loading. Game persistence through memcard may also consume this lane. FS is allowed to consume this lane, but public FS API shape belongs to the app-home filesystem agenda.
Decisao
Prometeu MUST introduce a third async work lane as the runtime-owned execution place for asset IO/decode work and compatible persistence IO work.
The async work lane MUST be serial: it has exactly one active job at a time.
The runtime MUST NOT create one OS thread per asset.load request in the normal
path.
Asset loading MUST use a backlog keyed by target bank_type/slot. A target can
have at most one current request. A newer request for the same bank_type/slot
MUST supersede the previous request for that target.
Asset handles MUST represent stable bank slots, not transient worker threads. A handle MUST remain queryable even when the slot has no valid loaded asset and even when there is no active request for that slot. The handle state MUST separate resident slot state from request/backlog state.
Asset IO/read and decode/materialization MUST run on the async work lane. Asset install/commit into resident banks MUST happen on the main runtime lane at predictable ownership points.
Memcard work MAY consume the same async work lane and has higher priority than ordinary asset loads when contention exists. FS MAY consume the same lane for IO-style work, but this decision does not define the public FS API.
Rationale
A serial lane maps cleanly to the intended third-core mental model and avoids a desktop-biased generic thread pool. It gives the runtime one place to observe IO/decode backlog, progress, priority, cancellation, and telemetry.
Keying requests by bank_type/slot makes the backlog naturally bounded by the
sum of bank slots. There is no need for a guest-visible queue_full state:
there cannot be unbounded distinct pending targets if each target has at most
one current request.
Keeping commit/install on the main lane preserves deterministic publication semantics and avoids letting the async lane mutate resident graphics/audio/scene state while the VM, render handoff, or frame boundary is observing it.
Separating slot_state and request_state inside the handle avoids ambiguity:
callers can inspect the slot at any time, while mutating operations still remain
protected by request generation.
Invariantes / Contrato
Async Work Lane
- The async work lane MUST be runtime-owned.
- The async work lane MUST be separate from the render worker.
- The async work lane MUST execute at most one active job at a time.
- The runtime MUST NOT use
thread::spawnper asset request in the normal asset loading path. - The host may implement the lane as a fixed worker thread. Hardware targets may map it to a dedicated core when available.
- If a physical third core is unavailable, the implementation MUST preserve the same logical lane contract.
Asset Backlog
- Asset requests MUST target a concrete
bank_type/slot. - The backlog is ordered and serial.
- Each
bank_type/slotMUST have at most one current request. - A newer request for the same
bank_type/slotMUST supersede any earlier pending request for that target. - If the target already contains the requested
asset_idas a valid resident asset,asset.loadMUST return a handle in ready state without adding a job to the backlog. - The effective backlog size is bounded by the sum of targetable bank slots, even though the contract does not expose a fixed queue limit.
- Implementations MUST collect stale metadata for canceled or superseded requests so the handle/request history does not grow without bound.
Handles
An asset handle MUST represent a stable bank slot target. The handle state MUST include at least:
handle:
bank_type
slot
slot_state:
loaded_asset_id
resident_state
slot_generation
request_state:
requested_asset_id
request_generation
state
backlog_position
progress
The handle MUST be queryable when:
- the slot is empty;
- the slot contains a valid resident asset;
- a request is queued;
- a request is active;
- a request is ready for commit;
- a request has been canceled;
- a request has been superseded;
- a request ended in error.
Mutating operations such as commit, cancel, promote, demote, or move MUST act on the current request generation. They MUST NOT accidentally mutate a newer request through an older handle view.
Superseding
superseded means a request was replaced by a newer request for the same
bank_type/slot.
- Superseding is an operational state, not a fault.
- If the old request is queued, it MUST be removed from the backlog.
- If the old request is active, the lane SHOULD cancel cooperatively when the current phase supports cheap cancellation.
- If active work cannot be interrupted cheaply, it MAY finish, but its result MUST be discarded when its generation no longer matches the target's current request generation.
Asset Backlog API Direction
The existing asset operations remain the base surface:
asset.load(asset_id, slot) -> (status, handle)
asset.status(handle) -> status
asset.commit(handle) -> status
asset.cancel(handle) -> status
The asset backlog surface SHOULD add a small status-first API:
asset.backlog_info()
-> (status, pending_count, active_handle, active_asset_id,
active_bank_type, active_slot, active_progress)
asset.backlog_position(handle)
-> (status, state, position, progress)
asset.backlog_move(handle, new_position)
-> status
asset.backlog_promote(handle)
-> status
asset.backlog_demote(handle)
-> status
asset.target_status(bank_type, slot)
-> (status, asset_id, handle, state, position, progress)
asset.backlog_promote(handle) is an official shortcut for moving a queued
request to position 1, the first pending position after the active job.
asset.backlog_demote(handle) is an official shortcut for moving a queued
request to the end of the pending backlog.
The final ABI names and exact return shapes may be refined during planning, but the implementation MUST preserve the capabilities above.
Progress
Progress MUST be represented without floating point. The recommended scale is
0..10000.
The initial phase model is:
queued -> 0
read -> 0..4000
decode -> 4000..9000
stage -> 9000..10000
ready -> 10000
If a phase cannot report internal progress, it MUST keep the previous progress mark and advance at phase completion. Implementations MUST NOT invent false precision for non-linear decode phases.
Telemetry
Telemetry MUST be cheap and must not add hot-path per-item decode cost.
Minimum telemetry:
- current backlog depth;
- target/request position;
- active job progress;
- jobs submitted;
- jobs completed;
- jobs failed;
- jobs canceled;
- jobs superseded;
- job duration;
- percentiles by
bank_type; - lightweight percentiles or small-window samples by
asset_id.
Percentiles MUST be updated when a job closes, not inside the inner decode loop.
Priority
When async lane consumers contend, the initial priority order is:
- memcard commit/write;
- FS write/config work;
- asset visual/audio/scene load;
- non-critical list/read work.
This priority order is a lane arbitration rule. It does not define public FS syscall semantics.
FS Boundary
FS MAY consume the async work lane for IO-style work.
This decision MUST NOT define the public FS API, app-home FS semantics, or
whether FS is exposed as request/poll or sync-appearing operations. Those
questions belong to AGD-0006.
AGD-0006 MUST be updated or interpreted with the existence of this async IO
lane in mind.
Impactos
Spec
docs/specs/runtime/15-asset-management.mdmust absorb the async lane, backlog, handle, superseding, progress, and telemetry contract.docs/specs/runtime/16-host-abi-and-syscalls.mdmust absorb any final public asset backlog syscall names and return shapes.docs/specs/runtime/16a-syscall-policies.mdmay need status catalog updates for superseded/canceled/backend unavailable states if not already covered by asset domain status.docs/specs/runtime/09-events-and-concurrency.mdshould mention the async work lane as an implementation-side lane that does not introduce guest callbacks.AGD-0006/ future FS spec work must account for FS as a possible consumer of the async IO lane.
Runtime
- Asset loading must stop spawning a thread per request.
- AssetManager needs a serial backlog keyed by
bank_type/slot. - Asset handles need stable slot identity plus separated
slot_stateandrequest_state. - Commit/install remains a main-lane operation.
- Superseding and generation checks become required correctness mechanisms.
Host
- Desktop host may implement the async work lane with one fixed worker thread.
- Host presentation/render worker remains separate and unaffected.
Firmware
- No direct firmware behavior is required for asset backlog mechanics.
- Firmware flows that rely on preload or memcard may later use lane telemetry or priority policy if needed.
Tooling
- Test tools may need support for inspecting backlog state and forcing request ordering.
- Asset fixtures should cover queued, active, ready, canceled, superseded, and already-resident paths.
Referencias
AGD-0008- Async Background Work Lanes for Assets and FS.docs/specs/runtime/15-asset-management.mddocs/specs/runtime/16-host-abi-and-syscalls.mddocs/specs/runtime/16a-syscall-policies.mddocs/specs/runtime/09-events-and-concurrency.mddocs/specs/runtime/08-save-memory-and-memcard.mdAGD-0006- App Home Filesystem Surface and Semantics.
Propagacao Necessaria
- Update asset management specs with the lane/backlog/handle contract.
- Update syscall specs if backlog APIs become public ABI.
- Update FS agenda/spec work to acknowledge the async IO lane without deciding FS public API in this decision.
- Plan implementation of a serial async work lane and asset backlog.
- Plan tests for superseding, stable slot handles, immediate-ready resident assets, progress, telemetry, and main-lane commit.
Revision Log
- 2026-06-28: Initial draft from
AGD-0008.