OpenClaw 測試堆記憶體洩漏
使用此技能進行測試記憶體調查。當有堆快照可用時,請勿僅根據 RSS 進行猜測。將快照名稱的差異視為初步分類證據,而非最終證明,除非有 retainers 或 dominators 支持此結論。
請先閱讀 ../openclaw-test-performance/SKILL.md,以了解當前的測試效能命令和證明路由。
對於執行時期修復(例如,長期運行的服務如 gateway 中的閉包洩漏),請參閱下方的驗證執行時期修復 —— 該部分使用的是專用 harness,而非單元測試剖析工作流程。
工作流程
-
首先重現失敗的型態。
-
匹配真實的進入點和工作執行緒(worker)預算。若要取得廣泛的 unit-fast 基線(包含各設定的最大 RSS 和頂層檔案計時),請從以下指令開始:
pnpm test:perf:groups \ --config test/vitest/vitest.unit-fast.config.ts \ --allow-failures \ --output .artifacts/test-perf/unit-fast-memory.json -
針對可疑的檔案,使用一個工作執行緒重新執行該檔案,並收集 wall/RSS 證據:
/usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose。 -
目前的
pnpm test執行是由scripts/test-projects.mts規劃。請記錄印出的 Vitest 設定或 shard,並在報告與設定或工作執行緒預算相關時保留該型態。
-
-
收集最強烈的可用堆記憶體證據。
- 執行
pnpm test:perf:profile:runner -- --output-dir .artifacts/test-perf/vitest-runner-profile -- <file>以取得單元執行器的 CPU 剖析和取樣堆記憶體剖析。在 DevTools 中開啟堆記憶體剖析,檢查最大的配置系列。 - 取樣的
.heapprofile輸出並非.heapsnapshot;請勿將其傳遞給快照差異輔助工具。 - 當專用 harness 或其他已知產生器產生重複的
.heapsnapshot檔案時,使用.agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs比較來自相同 PID 的快照。 - 如果沒有可用的快照或堆記憶體剖析,RSS 增長是很有用的初步分類證據,但請將洩漏分類保留為「非結論性」。
- 執行
-
在選擇修復方式之前,先對增長進行分類。
- 如果增長主要由 Vite/Vitest 轉換後的原始字串、
Module、system / Context、位元組碼、描述符陣列或屬性映射主導,則將其視為長期執行的工作執行緒中可能的保留模組圖增長。 - 如果增長主要由應用程式物件、快取、緩衝區、伺服器控制代碼、計時器、模擬狀態、sqlite 狀態或類似的執行時期物件主導,則將其視為可能的清理或生命週期洩漏。
- 如果名稱不明確,請在檢查 DevTools 中頂層差異的 retainers/dominators 之前,不要做出確定的標籤。
- 如果增長主要由 Vite/Vitest 轉換後的原始字串、
-
修復正確的層級。
- 對於共享工作執行緒中可能的保留轉換模組增長:
- 檢查所屬的 Vitest 設定和
scripts/test-projects.test-support.mjs中的程序分塊。僅在相同型態的證據顯示共享工作執行緒保留是原因時,才在那裡修復程序生命週期或專案所有權。 test/vitest/vitest.unit-fast-isolated.config.ts用於需要全新模組圖的經審計的狀態化測試。請勿將其用作通用的記憶體熱點列表。
- 檢查所屬的 Vitest 設定和
- 對於真正的洩漏:
- 修補受影響的測試或執行時期清理路徑。
- 尋找遺漏的
afterEach/afterAll、模組重置間隙、保留的全域狀態、未釋放的資料庫控制代碼,或存活超過檔案範圍的監聽器/計時器。
- 對於共享工作執行緒中可能的保留轉換模組增長:
-
使用最直接的證據進行驗證。
- 重新執行相同的分組或限定範圍命令,並確認最大 RSS 趨勢或 OOM 已減少。
- 當專用產生器發出堆記憶體剖析或快照時,重複相同的產生器並比較等效的產出物。
- 對於路由或設定更改,請驗證預期的 Vitest 設定或 shard 已啟動,且受影響的測試已完成。
啟發法則
- 不要將所有增長都稱為洩漏。在非隔離的共享 Vitest 專案中的增長,可能是工作執行緒生命週期問題,而非應用程式物件洩漏。
scripts/test-projects.mts、scripts/test-group-report.mts和scripts/run-vitest-profile.mts是目前的執行、分組 RSS 和剖析進入點。[test] starting ...行標識了要重現的 Vitest 設定或 shard。.artifacts/vitest-shard-timings.json儲存 config/shard 的持續時間以進行排程。它不是檔案級別的記憶體熱點或行為清單。- 當相同的保留物件系列在同一 Worker PID 的多個間隔中增長時,請相信快照而非直覺,然後使用 retainer 證據來確認不明確的調用。
快照比較
- 直接比較:
node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs before.heapsnapshot after.heapsnapshot
- 在同一通道中自動選取每個 PID 最早/最晚的快照:
node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs --lane-dir <snapshot-directory>
- 有用的標誌:
--top 40--min-kb 32--pid 16133
先閱讀正向最大的差異。模組轉換產出物的大量正向增長指向共享程序生命週期或專案所有權問題;執行時期物件的大量正向增長則暗示真正的洩漏。如果僅憑名稱無法解決問題,請在 DevTools 中開啟相同的快照對,檢查頂層行的 retainers/dominators,然後再宣告根本原因。
驗證執行時期修復(非測試記憶體)
以上工作流程適用於診斷 Vitest 工作執行緒記憶體增長。若要驗證執行時期/閉包修復實際上是否釋放了捕獲的狀態,請使用專用 harness:
pnpm leak:embedded-run— 執行scripts/embedded-run-abort-leak.ts。在模仿runEmbeddedAttempt的函數作用域中循環執行 N 個中止的執行,寫入堆快照,並使用FinalizationRegistry追蹤實例計數加上 RSS 差異,報告關於保留增長的 PASS/FAIL 判定。
模式:
closure-extracted(預設)— 生產環境修整形狀(helper 在模組作用域)。closure-inline— 修復前形狀(閉包在執行器作用域內)。用作敏感性檢查:如果它通過了,表示你破壞了 harness,而非修復了錯誤。synthetic-leak— 透過模組級別的儲存桶故意保留。用於在相信對真正修復的 PASS 判定之前,確認 harness 能夠檢測到洩漏。
快照存放在 .tmp/embedded-run-abort-leak/。使用與上述相同的腳本進行差異比較:
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
在修復不同的執行時期洩漏時,請在此 harness 旁新增一個新的 harness,而不是對其進行改造。確保 fixture 函數模擬洩漏所在函數的詞法作用域,而不是一個通用的中止循環。
輸出預期
使用此技能時,請回報:
- 精確的重現指令。
- 重現了哪個 Vitest 設定或目標,以及當快照可用時,比較的是哪個 PID。
- 當可用時,來自堆記憶體剖析或快照差異的主要保留物件系列。
- 問題是可能真正的洩漏,還是可能共享工作執行緒保留模組增長,以及 retainers/dominators 是否確認了這一點。
- 具體的修復或影響減少補丁。
- 你驗證了什麼,以及哪些快照開銷阻止了你進行驗證。