24 KiB
| id | ticket | title | status | created | resolved | decision | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| AGD-0008 | perf-async-background-work-lanes-for-assets-and-fs | Agenda - [PERF] Async Background Work Lanes for Assets and FS | accepted | 2026-03-27 | 2026-06-28 | DEC-0034 |
|
Agenda - [PERF] Async Background Work Lanes for Assets and FS
Contexto
Depois das decisoes de render worker, a concorrencia de render saiu do escopo desta agenda. O problema restante e a lane de trabalho assincrono para IO, decode e persistencia.
O modelo de execucao que queremos discutir agora assume uma terceira core/lane dedicada a trabalhos assincronos de runtime:
Core/lane 1: VM, firmware, SystemOS e frame logico
Core/lane 2: render worker / rasterizacao assincrona
Core/lane 3: async work lane para asset IO/decode e possivelmente FS
Essa terceira lane nao deve virar um pool grande e solto. A intencao e ter uma fila previsivel, com capacidade limitada, telemetria e regras de saturacao.
Para assets, a necessidade e concreta: asset.load() ainda cria uma thread do
SO por requisicao. Para FS, a motivacao provavel era evitar que operacoes de
leitura/escrita/listagem/delete, hoje chamadas de forma sincrona pelo dispatch,
roubem tempo do frame quando o backend real envolver IO. Precisamos verificar
se FS deve usar a mesma lane agora ou apenas reservar compatibilidade.
Problema
asset.load() hoje dispara thread::spawn por requisicao de asset nao
residente. Isso escala mal, cria jitter e deixa o runtime sem uma politica
unica de backpressure.
O dominio fs tambem tem operacoes potencialmente bloqueantes (read,
write, list_dir, delete, memcard commit/read/write) e a tabela de
syscalls ja as marca como nao deterministicas/custosas. Ainda assim, a
implementacao atual executa essas chamadas diretamente no caminho de dispatch.
Sem um contrato explicito, cada dominio pode inventar sua propria concorrencia: asset criando threads, FS bloqueando o frame, e memcard herdando comportamento acidental do backend.
Pontos Criticos
thread::spawnpor request escala mal e cria jitter.- A terceira core/lane deve ser um recurso de plataforma controlado, nao uma permissao geral para criar threads.
- Asset loading precisa de um pool/fila limitada para multiplos loads pendentes.
- FS pode justificar a mesma lane quando envolver IO real, mas nao devemos assinar uma API assincrona ampla sem entender a semantica de retorno.
- Commit/install em bancos residentes pode ter restricoes de determinismo e ownership; decode pode ser assincrono, mas publicacao/instalacao talvez tenha que cruzar de volta para a lane principal.
- A estrategia precisa funcionar em hardware alvo com poucos cores e tambem no host desktop sem transformar desktop em referencia de custo errada.
- Render worker ja tem seu proprio contrato e nao deve compartilhar essa lane.
Hotspots Atuais
- asset.rs -
AssetManager::load. - asset.rs -
thread::spawnpor load. - fs.rs -
fsememmarcados como superficies custosas/nao deterministicas. - dispatch.rs - chamadas
fs.*executam no dispatch. - virtual_fs.rs - VFS normaliza path e chama backend diretamente.
Alvo da Discussao
Fechar um modelo de terceira core/lane para trabalho assincrono de runtime, comecando por asset loading e avaliando se FS deve entrar na mesma lane no v1.
O alvo nao e criar multitarefa geral, callbacks para guest, ou um scheduler paralelo da VM. A lane deve executar trabalho de IO/decode/sync e publicar resultados observaveis por status/polling em pontos previsiveis.
O Que Precisa Ser Definido
-
Topologia da terceira lane. Definir:
- uma async work lane dedicada, separada da VM e do render worker;
- se a lane e uma thread fixa no host e uma core dedicada no hardware alvo;
- fallback quando nao houver core fisico dedicado;
- proibicao explicita de
thread::spawnpor request no caminho normal.
-
Pool/fila de asset loading. Definir:
- backlog de pedidos sem limite operacional fixo;
- exatamente um job ativo por vez na lane;
- o "pool" significa backlog e estados de job, nao multiplos workers reais;
- politica de substituicao/cancelamento para pedidos que miram o mesmo bank/slot;
- status para pending/backlog, ativo, progresso, cancelamento, erro de decode e sucesso.
-
Fases do asset load. Separar:
- request e alocacao de handle;
- leitura de bytes;
- decode/materializacao;
- staging de resultado;
- install/commit em banco residente.
Ponto a fechar: quais fases rodam na terceira lane e quais voltam para a lane principal por determinismo/ownership.
-
Inclusao de FS na mesma lane. Verificar se
fsdeve usar a terceira lane no v1 para:- leituras grandes;
- escritas;
- listagem;
- delete;
- memcard commit/read/write.
Ponto a fechar: FS sync publico continua bloqueante/status-first, vira request/poll, ou ganha apenas uma implementacao interna que drena trabalho em pontos de frame.
-
Separacao por dominio. Decidir se
assetefscompartilham uma fila com classes de prioridade ou se a terceira lane contem subfilas dedicadas. -
Politica de prioridade. Definir:
- prioridade de loads visuais vs audio;
- prioridade de save/config;
- prioridade de memcard commit;
- starvation prevention;
- limite de trabalho de instalacao/publicacao por frame.
-
Modelo de retorno e telemetria. Fechar como guest/runtime observa:
pending;loading;ready;canceled;backend_unavailable;- backlog;
- posicao de um asset no backlog;
- progresso percentual do asset ativo;
- drops/retries quando aplicavel.
-
Orcamento para hardware barato. Definir o baseline:
- VM principal;
- render worker;
- terceira async work lane;
- limites de memoria para jobs pendentes e buffers temporarios.
Opcoes
Opcao A - Terceira lane unica com subfilas e pool limitado de assets
- Abordagem: criar uma async work lane dedicada. Assets usam um pool/fila de jobs nessa lane; FS tambem pode submeter jobs na mesma lane, com prioridade menor ou classe separada.
- Pro: modelo simples de budget; evita explosao de threads; encaixa no alvo de terceira core; permite compartilhar backpressure e telemetria.
- Contra: assets e FS podem competir por IO; precisa politica clara para evitar que save/config fique atras de loads longos.
- Manutenibilidade: boa se a lane tiver tipos de job e estados explicitos.
Opcao B - Lane de assets agora, FS apenas reservado
- Abordagem: fechar v1 somente para asset loading, removendo
spawnpor request. FS continua sincrono por enquanto, mas o contrato da lane reserva compatibilidade para FS no futuro. - Pro: menor escopo; resolve a dor concreta de assets rapidamente; evita alterar semantica de FS antes da agenda de app home filesystem.
- Contra: nao resolve stalls reais de FS/memcard; pode exigir refatorar a lane depois para aceitar FS.
- Manutenibilidade: boa no curto prazo, com risco de segunda migracao.
Opcao C - Lanes separadas para asset e FS
- Abordagem: criar uma lane/pool para assets e outra para FS.
- Pro: isolamento forte; FS/save nao compete diretamente com loads de textura/audio/cena.
- Contra: consome mais threads/cores; contraria a premissa de terceira core unica; pior para hardware simples.
- Manutenibilidade: media; isolamento ajuda, mas a topologia fica pesada.
Opcao D - Pool global pequeno sem nocao de terceira lane
- Abordagem: trocar
spawnpor request por um pool global fixo. - Pro: simples de implementar em host desktop.
- Contra: perde a arquitetura de lanes por core; pode mascarar custo em desktop; fica menos claro como mapear para hardware alvo.
- Manutenibilidade: baixa para o modelo de console se virar dependencia acidental de scheduler do SO.
Sugestao / Recomendacao
Recomendo seguir com a Opcao A como direcao de arquitetura, mas fechar a decisao em duas camadas:
-
Contrato obrigatorio agora:
- terceira async work lane como recurso runtime;
- assets deixam de usar
thread::spawnpor requisicao; - asset loading passa por backlog consultavel/reordenavel;
- status, progresso e telemetria de backlog ficam explicitos.
-
Contrato condicionado para FS:
- FS deve ser analisado como consumidor legitimo da mesma lane porque suas operacoes podem envolver IO real e ja sao custosas/nao deterministicas;
- a decisao precisa escolher entre incluir FS no v1 ou reservar a lane para FS sem mudar ainda a superficie publica;
- memcard precisa ser considerado junto com FS, porque usa a mesma capacidade e backend de persistencia.
O ponto importante: a terceira core/lane deve ser a unidade de concorrencia. O pool de assets deve existir dentro desse modelo, nao como criacao livre de threads por load.
Respostas Consolidadas Ate Aqui
-
O pool de assets deve ser uma lane serial.
- Ha exatamente um job ativo na async work lane.
- O pool representa backlog, estados e controle de ordem, nao multiplos workers executando em paralelo.
-
O backlog de pedidos deve ser semanticamente ilimitado.
- Nao existe
queue_fullcomo status normal do dominio. - Como cada pedido mira um
bank_type/slote pedidos novos para o mesmo alvo substituem os anteriores, o backlog efetivo fica limitado pela soma dos slots dos bancos. - O contrato nao precisa expor limite de fila, mas a implementacao deve garantir que metadados de handles cancelados/superseded nao crescam sem coleta.
- Nao existe
-
Asset loading precisa expor consulta e reordenacao do backlog.
- Deve existir API publica/runtime para consultar onde um asset esta no backlog.
- Deve existir capacidade de adiantar ou atrasar um pedido de asset na fila.
- O asset ativo deve expor progresso percentual.
- Requests novos para o mesmo
bank_type/slotdevem cancelar pedidos anteriores pendentes para aquele alvo, evitando carregar asset que ja foi substituido antes de executar. - Se o alvo ja estiver carregado com o mesmo
asset_id, o novo request nao entra no backlog; ele retorna imediatamente pronto com handle disponivel. - Se um novo request substituir o alvo atualmente ativo, a lane deve cancelar em ponto cooperativo quando possivel; se o trabalho nao puder ser interrompido, o resultado antigo deve ser descartado ao final por geracao superseded.
supersededsignifica que um request foi substituido por outro request mais novo para o mesmobank_type/slot. Nao e erro e nao e cancelamento manual; e obsolescencia operacional do pedido antigo.- Handles representam o slot do bank como alvo estavel. Dentro do estado do handle deve existir uma secao de request/backlog para aquele slot, informando se ha pedido pendente/ativo, qual asset foi solicitado, progresso, posicao e geracao.
- Handles devem ser capazes de consultar o estado do alvo
bank_type/slota qualquer momento. Se nao houver asset valido carregado e tambem nao houver request pendente para aquele alvo, a consulta deve retornar estado explicito de vazio/invalido, nao falhar por ausencia de request ativo.
-
Decode/materializacao fria rodam na terceira lane.
- Leitura e decode de asset podem acontecer fora da lane principal.
- O resultado deve voltar como material pronto/staged.
-
Install/commit em bancos residentes continua na lane principal.
- A async lane prepara trabalho.
- A lane principal publica/instala em pontos previsiveis por ownership, determinismo e integracao com bancos/render/audio.
-
FS e memcard entram como consumidores possiveis da mesma lane, mas precisam ser melhor amarrados com a agenda de app home filesystem.
- A
DSC-0007/AGD-0006deve ser ajustada para estabelecer melhor a superficie publica e o modelo async/sync de FS. - Esta agenda deve afirmar a existencia da lane para trabalhos async de IO desse tipo e permitir que FS a consuma.
- Questoes de API publica de FS, request/poll e semantica de app home devem ficar a cargo da agenda 6.
- FS, memcard e assets nao devem operar sobrepostos quando isso gerar concorrencia real de backend; se houver competicao, a fila unica resolve a ordem.
- A
-
Prioridade quando houver competicao:
- memcard commit/write;
- FS write/config;
- asset visual/audio load;
- list/read nao critico.
-
Status-first continua valendo.
backend_unavailable,canceled, erro de decode e estados de job sao status operacionais.- Cancelamento por superseding de pedido no mesmo bank/slot e resultado operacional normal, nao fault.
- Nao ha
queue_fullno contrato esperado. - Trap permanece para violacao estrutural, como argumento invalido, capability ausente ou handle estruturalmente invalido.
-
Telemetria minima:
- backlog atual;
- posicao por asset;
- progresso percentual do asset ativo;
- jobs submetidos, concluidos, falhos e cancelados;
- tempo por job;
- percentis por asset enquanto estiver carregando na lane.
-
Escopo da agenda.
- O foco desta agenda e a lane, assets e memcard/game persistence.
- FS entra como consumidor permitido da lane de IO, mas as decisoes de API
publica de FS pertencem a
AGD-0006.
- API de backlog.
- A API inicial sugerida cobre os casos esperados.
asset.backlog_promote(handle)deve ser mantido como atalho oficial paraasset.backlog_move(handle, 1).asset.backlog_demote(handle)deve ser mantido como atalho oficial para mover o pedido para o fim dos pendentes.- Os atalhos nao criam semantica nova; apenas evitam que o caller calcule posicoes comuns.
- O handle deve permitir consulta estavel do slot do bank mesmo quando nao ha asset valido carregado.
- O estado do handle deve separar:
- estado do slot/residencia atual;
- estado do request/backlog atual para aquele slot.
- Operacoes que mudam estado, como commit/cancel/reorder, atuam sobre a secao de request/backlog do handle e precisam validar geracao/request atual para nao agir sobre um pedido substituido.
- Cancelamento por substituicao.
- Quando um request novo chega para o mesmo
bank_type/slot, o request anterior e marcado imediatamente comosuperseded. - Se o request anterior ainda estiver no backlog, ele sai da fila.
- Se o request anterior estiver ativo, a lane tenta cancelar cooperativamente quando a fase atual permitir.
- Se a fase ativa nao puder ser interrompida com baixo custo, ela termina e o resultado e descartado ao final porque a geracao do alvo ja mudou.
- Como o handle pertence ao slot do bank, ele continua consultavel; sua
secao de request deve indicar que a geracao anterior foi
supersededou que nao ha request ativo para aquele alvo.
- Fases de progresso.
read/decode/stagee suficiente como modelo minimo paraGLYPH,SOUNDeSCENE.- Subfases futuras podem detalhar progresso dentro de
decodesem mudar o contrato geral.
- Percentis.
- Percentis devem existir por
bank_typecomo telemetria principal. - Percentis por
asset_idtambem sao uteis, mas podem usar janela pequena ou agregador leve. - Percentis sao atualizados no fechamento do job, nao durante o hot path.
- Ajuste esperado na agenda de FS.
AGD-0006deve registrar que existe uma async IO lane compartilhavel por FS/memcard/assets.- Esta agenda so decide que FS pode consumir a lane.
- A API publica de FS, incluindo request/poll ou sync aparente, pertence a
AGD-0006.
API Inicial Sugerida Para Assets
Esta agenda ainda nao fecha nomes finais de ABI, mas a direcao inicial sugerida
e manter asset.load/status/commit/cancel e adicionar uma superficie pequena
para backlog:
-
asset.backlog_info() -> (status, pending_count, active_handle, active_asset_id, active_bank_type, active_slot, active_progress)- Consulta o estado geral da lane de assets.
active_progressusa escala inteira, preferencialmente0..10000para permitir duas casas decimais sem float.
-
asset.backlog_position(handle) -> (status, state, position, progress)- Consulta um pedido especifico.
position = 0significa job ativo.position > 0significa posicao no backlog.position = -1pode representar nao enfileirado/pronto/cancelado, dependendo dostate.
-
asset.backlog_move(handle, new_position) -> status- Move um pedido pendente para uma posicao absoluta.
new_position = 1significa primeiro pendente depois do ativo.- Nao interrompe diretamente o job ativo; substituicao do mesmo
bank_type/slotusa a regra de superseding.
-
asset.backlog_promote(handle) -> status- Atalho para mover o pedido para a frente do backlog pendente.
-
asset.backlog_demote(handle) -> status- Atalho para mover o pedido para o fim do backlog pendente.
-
asset.target_status(bank_type, slot) -> (status, asset_id, handle, state, position, progress)- Consulta por alvo operacional, que e o que realmente controla superseding.
- Ajuda quando o caller quer saber "o que esta programado para este slot".
Estados sugeridos para state:
empty;invalid;queued;active;ready;committed;canceled;superseded;error;backend_unavailable.
Regra de design: APIs de backlog sao operacionais e status-first. Reordenar um handle inexistente, ja concluido ou ja superseded retorna status operacional apropriado quando o handle e conhecido; violações estruturais continuam Trap.
Handles sao observaveis e representam slots de banks. Um handle conhecido pode
ser consultado mesmo quando o slot alvo nao contem asset valido carregado; nesse
caso a consulta deve retornar estado como empty ou invalid.
O estado do handle deve conter pelo menos duas partes:
slot_state:
loaded_asset_id
resident_state
request_state:
requested_asset_id
generation
queued/active/ready/canceled/superseded/error
backlog_position
progress
Mutacoes seguem regras mais estritas e devem falhar operacionalmente quando a secao de request/backlog do handle nao representa a geracao atual.
Progresso e Percentis
Progresso percentual
Sugestao:
- usar progresso em base inteira
0..10000; - quando o tamanho total de bytes for conhecido, calcular progresso por fases ponderadas;
- quando o decode nao for linear ou nao houver tamanho confiavel, expor progresso por fase com marcos conservadores.
Modelo inicial de fases:
queued -> 0
read -> 0..4000
decode -> 4000..9000
stage -> 9000..10000
ready -> 10000
Se uma fase nao consegue reportar progresso interno, ela deve manter o ultimo marco e saltar para o proximo marco ao concluir. Isso evita progresso falso sem perder visibilidade de fase.
Percentis por asset
Sugestao:
- registrar duracoes por asset no fim de cada job, nao por tick/frame;
- manter agregadores pequenos por
asset_ide porbank_type; - calcular percentis a partir de janela/ring buffer pequeno ou histograma barato por bucket de duracao;
- nunca atualizar percentis no loop quente de decode por item/pixel;
- o job ativo pode expor tempo decorrido e fase atual; percentis sao atualizados apenas no fechamento do job.
Perguntas em Aberto
- A agenda esta madura para virar decisao normativa ou ainda falta algum caso de asset/memcard/lane?
- A estrutura minima de
slot_stateerequest_stateno handle esta completa ou falta algum campo para commit/cancel/reorder seguro?
Resolucao
A agenda esta madura para decisao normativa.
A decisao deve formalizar:
- a terceira async work lane serial;
- um job ativo por vez;
- backlog efetivamente limitado pela unicidade de
bank_type/slot; - handles como observadores estaveis de slots de bank;
slot_stateerequest_statedentro do handle;- cancelamento/superseding por novo request para o mesmo alvo;
- load pronto imediato quando o asset ja esta residente no alvo;
- decode/materializacao na lane async;
- commit/install na lane principal;
- memcard como consumidor prioritario da mesma lane;
- FS como consumidor permitido, deixando API publica de FS para
AGD-0006; - progresso por fases e percentis atualizados no fechamento do job.
Dependencias
docs/specs/runtime/09-events-and-concurrency.mddocs/specs/runtime/15-asset-management.mddocs/specs/runtime/16a-syscall-policies.mddiscussion/workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md
Criterio para Encerrar
Pode virar PR quando houver decisao escrita sobre:
- existencia e papel da terceira async work lane;
- relacao dessa lane com a terceira core no hardware alvo e fallback no host;
- desenho do backlog serial de asset loading;
- regra de cancelamento/substituicao para requests no mesmo bank/slot;
- proibicao de
thread::spawnpor load no caminho normal; - fases de asset load que rodam na lane vs lane principal;
- inclusao condicionada de FS/memcard nessa mesma lane;
- API de consulta e reordenacao do backlog de assets;
- comportamento de request imediato quando o asset ja esta residente no alvo;
- regra de bounded backlog por unicidade de
bank_type/slot; - politica de prioridade entre asset, FS e memcard;
- status e telemetria para backlog, progresso, percentis, cancelamento e falha.
Discussao
- 2026-06-28: Agenda atualizada para remover render worker do escopo, assumir uma terceira core/lane para trabalhos assincronos, exigir pool/fila limitada para asset loading e reavaliar o papel de FS/memcard como consumidores da mesma lane.
- 2026-06-28: Discussao consolidou que a async work lane deve ser serial, com um job ativo, backlog semanticamente ilimitado, consulta/reordenacao publica de backlog de assets, progresso percentual do asset ativo, decode na lane async, install/commit na lane principal, e FS/memcard como consumidores a alinhar com a agenda de app home filesystem.
- 2026-06-28: Acrescentada regra de asset pipe: requests para o mesmo bank/slot devem cancelar pedidos anteriores pendentes para aquele alvo, tratando o pedido mais novo como substituto operacional.
- 2026-06-28: Discussao refinou que o alvo canonico e
bank_type/slot; um asset ja residente deve retornar handle pronto sem entrar no backlog; o backlog efetivo e limitado pela soma dos slots dos bancos; FS fica como consumidor permitido da lane, enquanto API publica de FS permanece na agenda 6; progresso deve usar fases conservadoras e percentis devem ser atualizados no fechamento do job, nao no hot path. - 2026-06-28: Open questions restantes foram consolidadas: API de backlog
aceita com
promote/demotecomo atalhos; superseding marca requests substituidos por outro request para o mesmobank_type/slot; cancelamento do ativo e cooperativo quando barato e descarte por geracao quando nao for;read/decode/stagecobre GLYPH/SOUND/SCENE; percentis ficam porbank_typee porasset_id; API publica de FS permanece emAGD-0006. - 2026-06-28: Adicionada a expectativa de que handles possam consultar o alvo
bank_type/slota qualquer momento, inclusive quando nao houver asset valido carregado; mutacoes devem continuar protegidas por request/geracao para evitar commit/cancel acidental de pedidos substituidos. - 2026-06-28: Refinado o modelo de handle: o handle pertence ao slot do bank e contem uma secao de estado de request/backlog para aquele slot. Consulta e residencia ficam estaveis por alvo; mutacoes atuam sobre a geracao atual do request dentro do handle.