337 lines
11 KiB
Markdown
337 lines
11 KiB
Markdown
---
|
|
id: DEC-0034
|
|
ticket: perf-async-background-work-lanes-for-assets-and-fs
|
|
title: Async Work Lane and Asset Backlog Contract
|
|
status: accepted
|
|
created: 2026-06-28
|
|
accepted: 2026-06-28
|
|
ref_agenda: AGD-0008
|
|
plans: [PLN-0123, PLN-0124, PLN-0125, PLN-0126, PLN-0127, PLN-0128]
|
|
tags: [perf, asset, fs, async, scheduler, runtime]
|
|
---
|
|
|
|
# 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:
|
|
|
|
```text
|
|
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::spawn` per 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/slot` MUST have at most one current request.
|
|
- A newer request for the same `bank_type/slot` MUST supersede any earlier
|
|
pending request for that target.
|
|
- If the target already contains the requested `asset_id` as a valid resident
|
|
asset, `asset.load` MUST 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
1. memcard commit/write;
|
|
2. FS write/config work;
|
|
3. asset visual/audio/scene load;
|
|
4. 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.md` must absorb the async lane,
|
|
backlog, handle, superseding, progress, and telemetry contract.
|
|
- `docs/specs/runtime/16-host-abi-and-syscalls.md` must absorb any final public
|
|
asset backlog syscall names and return shapes.
|
|
- `docs/specs/runtime/16a-syscall-policies.md` may 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.md` should 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_state` and
|
|
`request_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.md`
|
|
- `docs/specs/runtime/16-host-abi-and-syscalls.md`
|
|
- `docs/specs/runtime/16a-syscall-policies.md`
|
|
- `docs/specs/runtime/09-events-and-concurrency.md`
|
|
- `docs/specs/runtime/08-save-memory-and-memcard.md`
|
|
- `AGD-0006` - App Home Filesystem Surface and Semantics.
|
|
|
|
## Propagacao Necessaria
|
|
|
|
1. Update asset management specs with the lane/backlog/handle contract.
|
|
2. Update syscall specs if backlog APIs become public ABI.
|
|
3. Update FS agenda/spec work to acknowledge the async IO lane without deciding
|
|
FS public API in this decision.
|
|
4. Plan implementation of a serial async work lane and asset backlog.
|
|
5. 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`.
|