Vazamentos de Heap em Testes do OpenClaw
Use esta skill para investigações de memória em testes. Não adivinhe baseado apenas em RSS quando snapshots de heap estiverem disponíveis. Trate deltas de nomes de snapshot como evidência de triagem, não como prova, até que retentores ou dominadores suportem a alegação.
Leia primeiro ../openclaw-test-performance/SKILL.md para os comandos atuais de performance de teste e roteamento de prova.
Para correções em runtime (ex.: vazamentos de closure em serviços de longa duração, como o gateway), veja Validando correções em runtime abaixo — que usa um harness dedicado em vez do fluxo de trabalho de perfil de teste unitário.
Fluxo de Trabalho
-
Reproduza a forma com falha primeiro.
-
Corresponda ao entrypoint real e ao orçamento de workers. Para uma linha de base unit-fast ampla com RSS máximo por configuração e tempo por arquivo de topo, comece com:
pnpm test:perf:groups \ --config test/vitest/vitest.unit-fast.config.ts \ --allow-failures \ --output .artifacts/test-perf/unit-fast-memory.json -
Para um arquivo suspeito, execute novamente esse arquivo com um worker e colete evidências de wall/RSS:
/usr/bin/time -l pnpm test <arquivo> --maxWorkers=1 --reporter=verbose. -
A execução atual do
pnpm testé planejada porscripts/test-projects.mts. Registre a configuração do Vitest ou shard impressa e preserve essa forma quando o relatório for específico de configuração ou orçamento de worker.
-
-
Colete a evidência de heap mais forte disponível.
- Execute
pnpm test:perf:profile:runner -- --output-dir .artifacts/test-perf/vitest-runner-profile -- <arquivo>para um perfil de CPU mais um perfil de heap amostrado do runner unitário. Abra o perfil de heap no DevTools e inspecione as maiores famílias de alocação. - A saída
.heapprofileamostrada não é um.heapsnapshot; não a passe para o helper de delta de snapshot. - Quando um harness dedicado ou outro produtor conhecido emitir arquivos
.heapsnapshotrepetidos, compare os snapshots do mesmo PID com.agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs. - Se nenhum snapshot ou perfil de heap estiver disponível, o crescimento de RSS é evidência útil de triagem, mas mantenha a classificação do vazamento inconclusiva.
- Execute
-
Classifique o crescimento antes de escolher uma correção.
- Se o crescimento for dominado por strings de fonte transformadas pelo Vite/Vitest,
Module,system / Context, bytecode, arrays de descritor ou mapas de propriedade, trate como provável crescimento de grafo de módulo retido em workers de longa duração. - Se o crescimento for dominado por objetos da aplicação, caches, buffers, handles de servidor, timers, estado de mock, estado do sqlite ou objetos de runtime similares, trate como provável vazamento de limpeza ou ciclo de vida.
- Se os nomes forem ambíguos, pare antes de um rótulo confiante e inspecione retentores/dominadores no DevTools para os principais deltas.
- Se o crescimento for dominado por strings de fonte transformadas pelo Vite/Vitest,
-
Corrija a camada certa.
- Para provável crescimento de módulo transformado retido em workers compartilhados:
- Inspecione a configuração do Vitest proprietária e o chunking de processo em
scripts/test-projects.test-support.mjs. Corrija o tempo de vida do processo ou a propriedade do projeto apenas quando a evidência de mesma forma mostrar que a retenção do worker compartilhado é a causa. test/vitest/vitest.unit-fast-isolated.config.tsé para testes com estado auditados que precisam de um grafo de módulo novo. Não o use como uma lista genérica de hotspots de memória.- Para vazamentos reais:
- Corrija o teste ou caminho de limpeza de runtime implicado.
- Procure por
afterEach/afterAllausentes, lacunas de reset de módulo, estado global retido, handles de banco de dados não liberados ou listeners/timers que sobrevivem ao arquivo.
-
Verifique com a prova mais direta.
- Execute novamente o mesmo comando agrupado ou escopado e confirme que a tendência de RSS máximo ou OOM foi reduzida.
- Quando um produtor dedicado emitir perfis de heap ou snapshots, repita o mesmo produtor e compare artefatos equivalentes.
- Para alterações de roteamento ou configuração, verifique se a configuração esperada do Vitest ou shard é iniciada e os testes afetados são concluídos.
Heurísticas
- Não chame tudo de vazamento. O crescimento em um projeto Vitest compartilhado não isolado pode ser um problema de tempo de vida do worker, em vez de um vazamento de objeto da aplicação.
scripts/test-projects.mts,scripts/test-group-report.mtsescripts/run-vitest-profile.mtssão os entrypoints atuais de execução, RSS agrupado e perfil.- As linhas
[test] starting ...identificam a configuração do Vitest ou shard para reproduzir. .artifacts/vitest-shard-timings.jsonarmazena as durações de configuração/shards para agendamento. Não é um manifesto de hotspot de memória ou comportamento em nível de arquivo.- Quando as mesmas famílias de objetos retidos crescem em múltiplos intervalos no mesmo PID de worker, confie nos snapshots acima da intuição, depois confirme chamadas ambíguas com evidência de retentor.
Comparação de Snapshots
- Comparação direta:
node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs before.heapsnapshot after.heapsnapshot
- Selecionar snapshots mais antigos/mais novos automaticamente por PID dentro de uma mesma lane:
node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs --lane-dir <diretório-de-snapshots>
- Flags úteis:
--top 40--min-kb 32--pid 16133
Leia os deltas positivos principais primeiro. Grande crescimento positivo em artefatos de transformação de módulo aponta para tempo de vida de processo compartilhado ou propriedade de projeto; grande crescimento positivo em objetos de runtime sugere um vazamento real. Se os nomes sozinhos não resolverem, abra o mesmo par de snapshots no DevTools e inspecione retentores/dominadores para as linhas principais antes de declarar a causa raiz.
Validando correções em runtime (não memória de teste)
O fluxo de trabalho acima é para diagnosticar crescimento de memória de worker do Vitest. Para validar que uma correção de runtime/closure realmente libera o estado capturado, use o harness dedicado:
pnpm leak:embedded-run— executascripts/embedded-run-abort-leak.ts. Loop de N execuções abortadas em um escopo em forma de função que imitarunEmbeddedAttempt, escreve snapshots de heap e relata um veredito PASS/FAIL sobre o crescimento de retenção usandoFinalizationRegistrypara contagem de instâncias rastreadas mais delta de RSS.
Modos:
closure-extracted(padrão) — forma da correção de produção (helper no escopo do módulo).closure-inline— forma pré-correção (closure dentro do escopo do runner). Use como verificação de sensibilidade: se passar, você quebrou o harness, não corrigiu um bug.synthetic-leak— retém deliberadamente via um bucket em nível de módulo. Use para confirmar que o harness pode detectar vazamentos antes de confiar em um PASS em uma correção real.
Snapshots vão para .tmp/embedded-run-abort-leak/. Diferencie com o mesmo script
de cima:
node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs \
.tmp/embedded-run-abort-leak/baseline-*.heapsnapshot \
.tmp/embedded-run-abort-leak/batch-N-*.heapsnapshot --top 30
Ao corrigir um vazamento de runtime diferente, adicione um novo harness junto com este em vez de adaptá-lo. A função de fixture deve imitar o escopo léxico da função onde o vazamento reside, não ser um loop de aborto genérico.
Expectativas de Saída
Ao usar esta skill, relate:
- O comando de reprodução exato.
- Qual configuração do Vitest ou target foi reproduzido, e qual PID foi comparado quando snapshots estavam disponíveis.
- As famílias dominantes de objetos retidos do perfil de heap ou delta de snapshot, quando disponíveis.
- Se o problema é provavelmente um vazamento real ou provável crescimento de módulo retido de worker compartilhado, além de se retentores/dominadores confirmaram isso.
- A correção concreta ou patch de redução de impacto.
- O que você verificou, e qual overhead de snapshot impediu você de verificar.