fix: replace structuredClone with JSON cloning in session store cache#1923
Open
BingqingLyu wants to merge 3 commits into
Open
fix: replace structuredClone with JSON cloning in session store cache#1923BingqingLyu wants to merge 3 commits into
BingqingLyu wants to merge 3 commits into
Conversation
structuredClone uses Node.js's V8 ValueSerializer which allocates native C++ memory outside V8's managed heap. In the session store cache hot path (read + write on every request), these native buffers accumulate faster than GC can reclaim them, causing ~400MB+ native memory growth that eventually OOMs the gateway process (SIGABRT exit -6). Session stores are pure JSON data (no Maps, Sets, Dates, ArrayBuffers, or circular references), so JSON.parse(JSON.stringify()) is semantically equivalent and keeps all memory within V8's managed heap where GC can track and reclaim it. Benchmark (500 sessions × 10KB, 2000 clone cycles): - structuredClone: 473 MB RSS, 419 MB native growth - JSON clone: 141 MB RSS, 87 MB native growth (4.8x reduction) Fixes openclaw#45438
The initial 4 call sites (store-cache, store-load, auth-profiles) were the documented hot paths from openclaw#45438, but the gateway continued to OOM from structuredClone in other frequently-called paths: - secrets/runtime.ts: cloneSnapshot called on every config refresh (8 structuredClone calls → cloneJson helper) - gateway/call.ts: config cloned on every gateway credential resolution (3 calls) - config/io.ts: cloneUnknown used in merge-patch operations (1 call) - config/mutate.ts: full config cloned on every mutation (1 call) All data is JSON-safe (OpenClawConfig, AuthProfileStore, web tools metadata) — same safety rationale as the initial fix. Part of openclaw#45438
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces
structuredClonewithJSON.parse(JSON.stringify())at the 4 hot paths identified in openclaw#45438, eliminating the native memory leak that causes gateway OOM crashes (SIGABRT exit -6).Change Type
Scope
Linked Issue
Root Cause
structuredCloneuses V8'sValueSerializerwhich allocates native C++ memory outside V8's managed heap. In the session store cache hot path (read + write on every request), these native buffers accumulate faster than GC can reclaim them, causing ~400MB+ native memory growth that eventually OOMs the gateway.Session stores are pure JSON data (no Maps, Sets, Dates, ArrayBuffers, or circular references), so
JSON.parse(JSON.stringify())is semantically equivalent and keeps all memory within V8's managed heap where GC can track and reclaim it.Changes
src/config/sessions/store-cache.tsstructuredClonewithcloneStorehelper using JSON round-trip (cache read + write)src/config/sessions/store-load.tsstructuredClonewith JSON round-trip (store load return)src/agents/auth-profiles/store.tsstructuredClonewith JSON round-trip incloneAuthProfileStore3 files, +20/−4 lines.
Benchmark
Reproduction script: 500 sessions × 10KB each, 2000 read/write cache cycles (
--expose-gc):structuredClone(current)JSON.parse(JSON.stringify())(fix)The native memory column is the key metric — it's the memory V8 GC cannot see or reclaim. The fix reduces it from 419 MB to 87 MB (baseline Node.js overhead).
Why JSON cloning is safe here
JSON.parse(fs.readFileSync(...))— they cannot containundefined,NaN,Infinity,Date,Map,Set,RegExp,ArrayBuffer, or circular referencesapplySessionStoreMigrations,normalizeSessionStore) only set string/number/boolean/object/array valuesVerification
pnpm testpasses with 0 new failures (15 pre-existing failures on main, all unrelated — config legacy detection, secrets, web-search)auth-profiles.store-cache.test.tspasses (exercises the exact cache read/write path we changed)Security Impact