implements PLN-0127 async io lane boundaries

This commit is contained in:
bQUARKz 2026-06-28 14:14:10 +01:00
parent 961557583b
commit 8747c4f4cf
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 252 additions and 13 deletions

View File

@ -10,9 +10,10 @@ pub use prometeu_hal::{RenderWorkerBackend, RenderWorkerFrameSink};
pub use services::async_work::{ pub use services::async_work::{
AsyncWorkActiveJob, AsyncWorkCancelToken, AsyncWorkJobContext, AsyncWorkJobId, AsyncWorkActiveJob, AsyncWorkCancelToken, AsyncWorkJobContext, AsyncWorkJobId,
AsyncWorkJobKind, AsyncWorkJobOutcome, AsyncWorkLane, AsyncWorkLaneConfig, AsyncWorkJobKind, AsyncWorkJobOutcome, AsyncWorkLane, AsyncWorkLaneConfig,
AsyncWorkLaneController, AsyncWorkLaneError, AsyncWorkLaneTelemetry, AsyncWorkLaneController, AsyncWorkLaneError, AsyncWorkLaneTelemetry, AsyncWorkPriority,
}; };
pub use services::fs; pub use services::fs;
pub use services::memcard::MemcardAsyncLaneOperation;
pub use services::process; pub use services::process;
pub use services::task; pub use services::task;
pub use services::vm_runtime::{ pub use services::vm_runtime::{

View File

@ -29,6 +29,36 @@ pub enum AsyncWorkJobKind {
Test, Test,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsyncWorkPriority {
MemcardCommitWrite,
FsWriteConfig,
AssetLoad,
NonCriticalReadList,
}
impl AsyncWorkPriority {
const fn rank(self) -> u8 {
match self {
Self::MemcardCommitWrite => 0,
Self::FsWriteConfig => 1,
Self::AssetLoad => 2,
Self::NonCriticalReadList => 3,
}
}
}
impl AsyncWorkJobKind {
pub const fn default_priority(self) -> AsyncWorkPriority {
match self {
Self::Memcard => AsyncWorkPriority::MemcardCommitWrite,
Self::Fs => AsyncWorkPriority::FsWriteConfig,
Self::Asset => AsyncWorkPriority::AssetLoad,
Self::Test => AsyncWorkPriority::NonCriticalReadList,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsyncWorkJobOutcome { pub enum AsyncWorkJobOutcome {
Completed, Completed,
@ -59,6 +89,7 @@ impl Default for AsyncWorkLaneConfig {
pub struct AsyncWorkActiveJob { pub struct AsyncWorkActiveJob {
pub id: AsyncWorkJobId, pub id: AsyncWorkJobId,
pub kind: AsyncWorkJobKind, pub kind: AsyncWorkJobKind,
pub priority: AsyncWorkPriority,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@ -131,6 +162,7 @@ impl AsyncWorkCancelToken {
pub struct AsyncWorkJobContext { pub struct AsyncWorkJobContext {
pub job_id: AsyncWorkJobId, pub job_id: AsyncWorkJobId,
pub kind: AsyncWorkJobKind, pub kind: AsyncWorkJobKind,
pub priority: AsyncWorkPriority,
cancel_token: AsyncWorkCancelToken, cancel_token: AsyncWorkCancelToken,
} }
@ -145,6 +177,7 @@ type AsyncWorkFn = Box<dyn FnOnce(AsyncWorkJobContext) -> AsyncWorkJobOutcome +
struct PendingAsyncWorkJob { struct PendingAsyncWorkJob {
id: AsyncWorkJobId, id: AsyncWorkJobId,
kind: AsyncWorkJobKind, kind: AsyncWorkJobKind,
priority: AsyncWorkPriority,
cancel_token: AsyncWorkCancelToken, cancel_token: AsyncWorkCancelToken,
work: AsyncWorkFn, work: AsyncWorkFn,
} }
@ -152,6 +185,7 @@ struct PendingAsyncWorkJob {
struct RunningAsyncWorkJob { struct RunningAsyncWorkJob {
id: AsyncWorkJobId, id: AsyncWorkJobId,
kind: AsyncWorkJobKind, kind: AsyncWorkJobKind,
priority: AsyncWorkPriority,
cancel_token: AsyncWorkCancelToken, cancel_token: AsyncWorkCancelToken,
work: AsyncWorkFn, work: AsyncWorkFn,
} }
@ -177,6 +211,15 @@ impl AsyncWorkLane {
&self, &self,
kind: AsyncWorkJobKind, kind: AsyncWorkJobKind,
work: impl FnOnce(AsyncWorkJobContext) -> AsyncWorkJobOutcome + Send + 'static, work: impl FnOnce(AsyncWorkJobContext) -> AsyncWorkJobOutcome + Send + 'static,
) -> Result<AsyncWorkJobId, AsyncWorkLaneError> {
self.submit_with_priority(kind, kind.default_priority(), work)
}
pub fn submit_with_priority(
&self,
kind: AsyncWorkJobKind,
priority: AsyncWorkPriority,
work: impl FnOnce(AsyncWorkJobContext) -> AsyncWorkJobOutcome + Send + 'static,
) -> Result<AsyncWorkJobId, AsyncWorkLaneError> { ) -> Result<AsyncWorkJobId, AsyncWorkLaneError> {
let mut state = self.state.lock().map_err(|_| AsyncWorkLaneError::InternalFailure)?; let mut state = self.state.lock().map_err(|_| AsyncWorkLaneError::InternalFailure)?;
if state.shutdown_requested { if state.shutdown_requested {
@ -186,18 +229,52 @@ impl AsyncWorkLane {
state.next_job_id += 1; state.next_job_id += 1;
let id = AsyncWorkJobId::new(state.next_job_id); let id = AsyncWorkJobId::new(state.next_job_id);
let cancel_token = AsyncWorkCancelToken::new(); let cancel_token = AsyncWorkCancelToken::new();
state.pending.push_back(PendingAsyncWorkJob { let job = PendingAsyncWorkJob { id, kind, priority, cancel_token, work: Box::new(work) };
id, let insert_at = state
kind, .pending
cancel_token, .iter()
work: Box::new(work), .position(|pending| priority.rank() < pending.priority.rank())
}); .unwrap_or(state.pending.len());
state.pending.insert(insert_at, job);
let pending_depth = state.pending.len(); let pending_depth = state.pending.len();
state.telemetry.record_submitted(id, pending_depth); state.telemetry.record_submitted(id, pending_depth);
self.ready.notify_one(); self.ready.notify_one();
Ok(id) Ok(id)
} }
pub fn submit_memcard_commit_write(
&self,
work: impl FnOnce(AsyncWorkJobContext) -> AsyncWorkJobOutcome + Send + 'static,
) -> Result<AsyncWorkJobId, AsyncWorkLaneError> {
self.submit_with_priority(
AsyncWorkJobKind::Memcard,
AsyncWorkPriority::MemcardCommitWrite,
work,
)
}
pub fn submit_fs_write_config(
&self,
work: impl FnOnce(AsyncWorkJobContext) -> AsyncWorkJobOutcome + Send + 'static,
) -> Result<AsyncWorkJobId, AsyncWorkLaneError> {
self.submit_with_priority(AsyncWorkJobKind::Fs, AsyncWorkPriority::FsWriteConfig, work)
}
pub fn submit_asset_load(
&self,
work: impl FnOnce(AsyncWorkJobContext) -> AsyncWorkJobOutcome + Send + 'static,
) -> Result<AsyncWorkJobId, AsyncWorkLaneError> {
self.submit_with_priority(AsyncWorkJobKind::Asset, AsyncWorkPriority::AssetLoad, work)
}
pub fn submit_non_critical_read_list(
&self,
kind: AsyncWorkJobKind,
work: impl FnOnce(AsyncWorkJobContext) -> AsyncWorkJobOutcome + Send + 'static,
) -> Result<AsyncWorkJobId, AsyncWorkLaneError> {
self.submit_with_priority(kind, AsyncWorkPriority::NonCriticalReadList, work)
}
pub fn request_cancel(&self, job_id: AsyncWorkJobId) -> bool { pub fn request_cancel(&self, job_id: AsyncWorkJobId) -> bool {
let state = self.state.lock().unwrap(); let state = self.state.lock().unwrap();
if state.active.is_some_and(|active| active.id == job_id) { if state.active.is_some_and(|active| active.id == job_id) {
@ -253,7 +330,11 @@ impl AsyncWorkLane {
} }
if let Some(pending) = state.pending.pop_front() { if let Some(pending) = state.pending.pop_front() {
let active = AsyncWorkActiveJob { id: pending.id, kind: pending.kind }; let active = AsyncWorkActiveJob {
id: pending.id,
kind: pending.kind,
priority: pending.priority,
};
state.active = Some(active); state.active = Some(active);
state.active_cancel_token = Some(pending.cancel_token.clone()); state.active_cancel_token = Some(pending.cancel_token.clone());
let pending_depth = state.pending.len(); let pending_depth = state.pending.len();
@ -261,6 +342,7 @@ impl AsyncWorkLane {
return Some(RunningAsyncWorkJob { return Some(RunningAsyncWorkJob {
id: pending.id, id: pending.id,
kind: pending.kind, kind: pending.kind,
priority: pending.priority,
cancel_token: pending.cancel_token, cancel_token: pending.cancel_token,
work: pending.work, work: pending.work,
}); });
@ -332,8 +414,12 @@ impl Drop for AsyncWorkLaneController {
fn run_async_work_loop(lane: Arc<AsyncWorkLane>) { fn run_async_work_loop(lane: Arc<AsyncWorkLane>) {
while let Some(job) = lane.wait_take() { while let Some(job) = lane.wait_take() {
let context = let context = AsyncWorkJobContext {
AsyncWorkJobContext { job_id: job.id, kind: job.kind, cancel_token: job.cancel_token }; job_id: job.id,
kind: job.kind,
priority: job.priority,
cancel_token: job.cancel_token,
};
let result = catch_unwind(AssertUnwindSafe(|| (job.work)(context))) let result = catch_unwind(AssertUnwindSafe(|| (job.work)(context)))
.unwrap_or(AsyncWorkJobOutcome::Failed); .unwrap_or(AsyncWorkJobOutcome::Failed);
lane.record_finished(job.id, result); lane.record_finished(job.id, result);
@ -379,6 +465,60 @@ mod tests {
assert_eq!(telemetry.completed_jobs, 2); assert_eq!(telemetry.completed_jobs, 2);
} }
#[test]
fn async_work_lane_runs_higher_priority_io_before_assets() {
let lane = Arc::new(AsyncWorkLane::default());
let (tx, rx) = mpsc::channel();
lane.submit_asset_load({
let tx = tx.clone();
move |ctx| {
tx.send((ctx.kind, ctx.priority)).unwrap();
AsyncWorkJobOutcome::Completed
}
})
.unwrap();
lane.submit_non_critical_read_list(AsyncWorkJobKind::Fs, {
let tx = tx.clone();
move |ctx| {
tx.send((ctx.kind, ctx.priority)).unwrap();
AsyncWorkJobOutcome::Completed
}
})
.unwrap();
lane.submit_memcard_commit_write({
let tx = tx.clone();
move |ctx| {
tx.send((ctx.kind, ctx.priority)).unwrap();
AsyncWorkJobOutcome::Completed
}
})
.unwrap();
lane.submit_fs_write_config({
let tx = tx.clone();
move |ctx| {
tx.send((ctx.kind, ctx.priority)).unwrap();
AsyncWorkJobOutcome::Completed
}
})
.unwrap();
let mut controller =
AsyncWorkLaneController::start(AsyncWorkLaneConfig::default(), Arc::clone(&lane));
assert_eq!(
rx.recv().unwrap(),
(AsyncWorkJobKind::Memcard, AsyncWorkPriority::MemcardCommitWrite)
);
assert_eq!(rx.recv().unwrap(), (AsyncWorkJobKind::Fs, AsyncWorkPriority::FsWriteConfig));
assert_eq!(rx.recv().unwrap(), (AsyncWorkJobKind::Asset, AsyncWorkPriority::AssetLoad));
assert_eq!(
rx.recv().unwrap(),
(AsyncWorkJobKind::Fs, AsyncWorkPriority::NonCriticalReadList)
);
controller.stop().unwrap();
}
#[test] #[test]
fn async_work_lane_has_one_active_job() { fn async_work_lane_has_one_active_job() {
let lane = Arc::new(AsyncWorkLane::default()); let lane = Arc::new(AsyncWorkLane::default());

View File

@ -8,4 +8,4 @@ pub use fs_backend::FsBackend;
pub use fs_entry::FsEntry; pub use fs_entry::FsEntry;
pub use fs_error::FsError; pub use fs_error::FsError;
pub use fs_state::FsState; pub use fs_state::FsState;
pub use virtual_fs::VirtualFS; pub use virtual_fs::{FsAsyncLaneOperation, VirtualFS};

View File

@ -1,4 +1,5 @@
use crate::fs::{FsBackend, FsEntry, FsError}; use crate::fs::{FsBackend, FsEntry, FsError};
use crate::services::async_work::{AsyncWorkJobKind, AsyncWorkPriority};
/// Virtual Filesystem (VFS) interface for Prometeu. /// Virtual Filesystem (VFS) interface for Prometeu.
/// ///
@ -13,6 +14,29 @@ pub struct VirtualFS {
backend: Option<Box<dyn FsBackend>>, backend: Option<Box<dyn FsBackend>>,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FsAsyncLaneOperation {
Write,
Delete,
Config,
Read,
ListDir,
Exists,
}
impl FsAsyncLaneOperation {
pub const fn job_kind(self) -> AsyncWorkJobKind {
AsyncWorkJobKind::Fs
}
pub const fn priority(self) -> AsyncWorkPriority {
match self {
Self::Write | Self::Delete | Self::Config => AsyncWorkPriority::FsWriteConfig,
Self::Read | Self::ListDir | Self::Exists => AsyncWorkPriority::NonCriticalReadList,
}
}
}
impl Default for VirtualFS { impl Default for VirtualFS {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -242,4 +266,18 @@ mod tests {
assert!(matches!(vfs.delete("/"), Err(FsError::PermissionDenied))); assert!(matches!(vfs.delete("/"), Err(FsError::PermissionDenied)));
assert_eq!(calls.delete.load(Ordering::Relaxed), 0); assert_eq!(calls.delete.load(Ordering::Relaxed), 0);
} }
#[test]
fn fs_async_lane_operations_classify_priority_without_public_api_changes() {
assert_eq!(FsAsyncLaneOperation::Write.job_kind(), AsyncWorkJobKind::Fs);
assert_eq!(FsAsyncLaneOperation::Write.priority(), AsyncWorkPriority::FsWriteConfig);
assert_eq!(FsAsyncLaneOperation::Delete.priority(), AsyncWorkPriority::FsWriteConfig);
assert_eq!(FsAsyncLaneOperation::Config.priority(), AsyncWorkPriority::FsWriteConfig);
assert_eq!(FsAsyncLaneOperation::Read.priority(), AsyncWorkPriority::NonCriticalReadList);
assert_eq!(
FsAsyncLaneOperation::ListDir.priority(),
AsyncWorkPriority::NonCriticalReadList
);
assert_eq!(FsAsyncLaneOperation::Exists.priority(), AsyncWorkPriority::NonCriticalReadList);
}
} }

View File

@ -1,4 +1,5 @@
use crate::fs::{FsError, VirtualFS}; use crate::fs::{FsError, VirtualFS};
use crate::services::async_work::{AsyncWorkJobKind, AsyncWorkPriority};
use std::collections::HashMap; use std::collections::HashMap;
pub const MEMCARD_SLOT_COUNT: usize = 32; pub const MEMCARD_SLOT_COUNT: usize = 32;
@ -50,6 +51,30 @@ pub struct MemcardWriteResult {
pub bytes_written: u32, pub bytes_written: u32,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemcardAsyncLaneOperation {
SlotWrite,
SlotCommit,
SlotClear,
SlotRead,
SlotStat,
}
impl MemcardAsyncLaneOperation {
pub const fn job_kind(self) -> AsyncWorkJobKind {
AsyncWorkJobKind::Memcard
}
pub const fn priority(self) -> AsyncWorkPriority {
match self {
Self::SlotWrite | Self::SlotCommit | Self::SlotClear => {
AsyncWorkPriority::MemcardCommitWrite
}
Self::SlotRead | Self::SlotStat => AsyncWorkPriority::NonCriticalReadList,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct SlotImage { struct SlotImage {
payload: Vec<u8>, payload: Vec<u8>,
@ -428,4 +453,29 @@ mod tests {
let stat = mem.slot_stat(&fs, 7, 2); let stat = mem.slot_stat(&fs, 7, 2);
assert_eq!(stat.state, MemcardSlotState::Corrupt); assert_eq!(stat.state, MemcardSlotState::Corrupt);
} }
#[test]
fn memcard_async_lane_operations_classify_priority_without_changing_public_api() {
assert_eq!(MemcardAsyncLaneOperation::SlotWrite.job_kind(), AsyncWorkJobKind::Memcard);
assert_eq!(
MemcardAsyncLaneOperation::SlotWrite.priority(),
AsyncWorkPriority::MemcardCommitWrite
);
assert_eq!(
MemcardAsyncLaneOperation::SlotCommit.priority(),
AsyncWorkPriority::MemcardCommitWrite
);
assert_eq!(
MemcardAsyncLaneOperation::SlotClear.priority(),
AsyncWorkPriority::MemcardCommitWrite
);
assert_eq!(
MemcardAsyncLaneOperation::SlotRead.priority(),
AsyncWorkPriority::NonCriticalReadList
);
assert_eq!(
MemcardAsyncLaneOperation::SlotStat.priority(),
AsyncWorkPriority::NonCriticalReadList
);
}
} }

View File

@ -18,7 +18,7 @@
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-06-04","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0026","file":"discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-06-04","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0026","file":"discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
{"type":"discussion","id":"DSC-0009","status":"in_progress","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-06-28","tags":["perf","asset","fs","async","scheduler","runtime"],"agendas":[{"id":"AGD-0008","file":"AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-06-28"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-async-work-lane-and-asset-backlog-contract.md","status":"accepted","created_at":"2026-06-28","updated_at":"2026-06-28","ref_agenda":"AGD-0008"}],"plans":[{"id":"PLN-0123","file":"PLN-0123-async-work-lane-specification-propagation.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0124","file":"PLN-0124-async-work-lane-runtime-infrastructure.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0125","file":"PLN-0125-asset-backlog-and-stable-slot-handles.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0126","file":"PLN-0126-asset-backlog-public-api-and-status-surface.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0127","file":"PLN-0127-async-lane-memcard-and-fs-integration-boundaries.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0128","file":"PLN-0128-async-asset-pipeline-contract-tests-and-telemetry.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]}],"lessons":[]} {"type":"discussion","id":"DSC-0009","status":"in_progress","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-06-28","tags":["perf","asset","fs","async","scheduler","runtime"],"agendas":[{"id":"AGD-0008","file":"AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-06-28"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-async-work-lane-and-asset-backlog-contract.md","status":"accepted","created_at":"2026-06-28","updated_at":"2026-06-28","ref_agenda":"AGD-0008"}],"plans":[{"id":"PLN-0123","file":"PLN-0123-async-work-lane-specification-propagation.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0124","file":"PLN-0124-async-work-lane-runtime-infrastructure.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0125","file":"PLN-0125-asset-backlog-and-stable-slot-handles.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0126","file":"PLN-0126-asset-backlog-public-api-and-status-surface.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0127","file":"PLN-0127-async-lane-memcard-and-fs-integration-boundaries.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0128","file":"PLN-0128-async-asset-pipeline-contract-tests-and-telemetry.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0010","status":"done","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0036","file":"discussion/lessons/DSC-0010-perf-host-desktop-frame-pacing-and-presentation/LSN-0036-frame-publication-and-host-invalidation-must-be-separate.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]} {"type":"discussion","id":"DSC-0010","status":"done","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0036","file":"discussion/lessons/DSC-0010-perf-host-desktop-frame-pacing-and-presentation/LSN-0036-frame-publication-and-host-invalidation-must-be-separate.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]}
{"type":"discussion","id":"DSC-0011","status":"abandoned","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-06-28","tags":[],"agendas":[{"id":"AGD-0010","file":"AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"abandoned","created_at":"2026-03-27","updated_at":"2026-06-28","_override_reason":"User explicitly chose to remove this agenda because it no longer applies to the current render scenario; future optimizations can be discussed when needed."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to abandon this discussion because it no longer applies to the current render scenario; future optimizations can be discussed when needed."} {"type":"discussion","id":"DSC-0011","status":"abandoned","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-06-28","tags":[],"agendas":[{"id":"AGD-0010","file":"AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"abandoned","created_at":"2026-03-27","updated_at":"2026-06-28","_override_reason":"User explicitly chose to remove this agenda because it no longer applies to the current render scenario; future optimizations can be discussed when needed."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to abandon this discussion because it no longer applies to the current render scenario; future optimizations can be discussed when needed."}
{"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"discussion/lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]} {"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"discussion/lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]}

View File

@ -26,6 +26,16 @@ Sem um contrato claro de `home` por app, a API tende a crescer com semantica inc
agenda deve decidir a API publica de FS considerando essa lane, mas a agenda deve decidir a API publica de FS considerando essa lane, mas a
existencia da lane ja esta fechada por `DEC-0034`. existencia da lane ja esta fechada por `DEC-0034`.
## Fronteira com a Async IO Lane
`DEC-0034` fecha a existencia de uma lane serial para trabalho async de IO. FS
pode consumir essa lane internamente para escrita, configuracao, leitura ou
listagem, conforme prioridade operacional definida pelo runtime.
Esta agenda continua dona da decisao sobre API publica de FS para `app home`.
Portanto, a existencia da lane nao implica criar agora request handles, polling
publico ou novas syscalls de FS. Esses shapes permanecem em aberto aqui.
## Alvo da Discussao ## Alvo da Discussao
Fechar a superficie e semantica funcional de `fs` para perfil `app`, com sandbox por `app_id` e transporte de bytes via decisao `003`. Fechar a superficie e semantica funcional de `fs` para perfil `app`, com sandbox por `app_id` e transporte de bytes via decisao `003`.

View File

@ -2,7 +2,7 @@
id: PLN-0127 id: PLN-0127
ticket: perf-async-background-work-lanes-for-assets-and-fs ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Async Lane Memcard and FS Integration Boundaries title: Async Lane Memcard and FS Integration Boundaries
status: open status: done
created: 2026-06-28 created: 2026-06-28
completed: completed:
ref_decisions: [DEC-0034] ref_decisions: [DEC-0034]