You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(memory): close app-service leak paths behind the per-task memory ramp
Prod app tasks climb from ~3.0 GB to 9+ GB average (20.6 GB worst task)
with uptime and reset only on deploy. The growth lives in the main Next.js
process — the isolated-vm worker is a separate, bounded child and its
disposal already runs in finally on every path. Four fixes at the sites
that can actually accumulate there:
- copilot stream teardown (lib/copilot/request/lifecycle/start.ts): the
activeStreams registration, the 250ms Redis abort poller, and the SSE
keepalive were acquired outside the try whose finally releases them, so
a throw before the lifecycle started (e.g. resetBuffer on a Redis blip)
or a throw inside the ordered teardown orphaned two immortal intervals
and the registration. Add an idempotent backstop in the orchestration's
outer finally; the ordered teardown sets a flag so the normal path pays
nothing.
- large-value cache (lib/execution/payloads/cache.ts): expiry was enforced
only inside later cache calls, so on a quieting instance the last
entries — parsed object graphs worth a multiple of their JSON-byte
accounting — sat indefinitely instead of for the 15-minute TTL. Add a
self-retiring, unref'd sweep interval, and export occupancy stats.
- memory telemetry (lib/monitoring/memory-telemetry.ts): add the
large-value cache occupancy and detached-context count to the periodic
snapshot so the JSON-bytes-vs-heap amplification and context retention
are readable from the same log line as heapUsedMB.
- BYOK rotation cursors (lib/api-key/byok.ts): the tenant-keyed cursor Map
had no delete, TTL, or ceiling. Bound it with an LRU; evicting an idle
pool's cursor just restarts its rotation at index 0.
- collab-doc converter (lib/collab-doc/converter.ts): the DOM guard read
the bundled module's `window` binding while the install wrote
`globalThis.window` — the same bundler mismatch documented for TipTap in
next.config.ts — so a runtime where the two disagree re-allocated a
multi-MB jsdom window on every conversion. Guard and install now go
through globalThis, and the jsdom window is a module singleton either
way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0 commit comments