Skip to content

fix: replace structuredClone with JSON cloning in session store cache#1923

Open
BingqingLyu wants to merge 3 commits into
mainfrom
fork-pr-60444-fix-structuredclone-memory-leak
Open

fix: replace structuredClone with JSON cloning in session store cache#1923
BingqingLyu wants to merge 3 commits into
mainfrom
fork-pr-60444-fix-structuredclone-memory-leak

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 28, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces structuredClone with JSON.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

  • Bug fix

Scope

  • Gateway / orchestration

Linked Issue

Root Cause

structuredClone uses V8's 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.

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

File Change
src/config/sessions/store-cache.ts Replace structuredClone with cloneStore helper using JSON round-trip (cache read + write)
src/config/sessions/store-load.ts Replace structuredClone with JSON round-trip (store load return)
src/agents/auth-profiles/store.ts Replace structuredClone with JSON round-trip in cloneAuthProfileStore

3 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)
RSS 473 MB 141 MB
Native memory growth 419 MB 87 MB
Reduction 4.8×

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

  • Session stores originate from JSON.parse(fs.readFileSync(...)) — they cannot contain undefined, NaN, Infinity, Date, Map, Set, RegExp, ArrayBuffer, or circular references
  • Runtime mutations (applySessionStoreMigrations, normalizeSessionStore) only set string/number/boolean/object/array values
  • Auth profile stores follow the same pattern — loaded from JSON, pure data types

Verification

  • pnpm test passes with 0 new failures (15 pre-existing failures on main, all unrelated — config legacy detection, secrets, web-search)
  • auth-profiles.store-cache.test.ts passes (exercises the exact cache read/write path we changed)
  • Benchmark script available for independent reproduction

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: structuredClone in session store cache causes native memory leak (~1GB/min)

2 participants