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: cap in-memory journal to prevent heap OOM under sustained load (#114)
## Summary
`Journal.entries` was unbounded — every request across 26 handlers (~179
call sites: chat completions, messages, responses, gemini, bedrock,
embeddings, images, speech, transcription, video, ollama, cohere,
search, rerank, moderation, a2a, mcp, agui, ws-*, etc.) pushed a full `{
method, path, headers, body, response }` record and never evicted. At
sustained prod traffic this grows heap ~3.8MB/sec → 4GB → OOM in ~18
minutes. Observed exactly that on `showcase-aimock` Railway service:
deterministic 0→4GB heap growth then `FATAL ERROR: Reached heap limit
Allocation failed`. Crash cascades to ~7 downstream showcase services
that route through aimock via `OPENAI_BASE_URL`.
## Fix
FIFO size cap on `Journal.entries` via new `JournalOptions.maxEntries`.
Default:
- Programmatic (`new Journal()` / `createServer()`): **unbounded**
(backwards-compat; 100+ test/library callers depend on this).
- CLI: **1000** (default for `serve` / the GHCR image). Override via
`--journal-max <N>`; `0` or omitted = unbounded.
Eviction is a single `shift()` per over-cap add. At cap=1000 ×
~5KB/entry ≈ 5MB steady-state — well under heap limits.
## Test plan
- [x] 6 new red-green tests in `src/__tests__/journal.test.ts` (cap
behavior, FIFO ordering, uncapped default, `getLast`/`findByFixture`
post-eviction, 100k-add cap invariant)
- [x] Full suite: 2449 tests pass
- [x] Lint + build + prettier clean
- [ ] Post-merge: GHCR image republishes with the fix; Railway picks up
on next drift-rebuild or manual redeploy
## Follow-ups (separate PR)
- CLI: reject negative `--journal-max` values (currently silently
treated as unbounded)
- `createServer()` default: flip to finite cap so long-running embedders
don't inherit the leak
- `fixtureMatchCountsByTestId` Map cap (narrower but also unbounded)
- Correct the "amortized O(1)" comment — `Array.shift` is O(n); true
O(1) would need a ring buffer
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,11 @@
1
1
# @copilotkit/aimock
2
2
3
+
## 1.14.1
4
+
5
+
### Patch Changes
6
+
7
+
- Cap in-memory journal (and fixture-match-counts map) to prevent heap OOM under sustained load. `Journal.entries` was unbounded, causing heap growth ~3.8MB/sec to 4GB → OOM in ~18 minutes on production Railway deployments. Default cap for CLI (`serve`) is now 1000 entries; programmatic `createServer()` remains unbounded by default (back-compat). See `--journal-max` flag.
Copy file name to clipboardExpand all lines: package.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
{
2
2
"name": "@copilotkit/aimock",
3
-
"version": "1.14.0",
3
+
"version": "1.14.1",
4
4
"description": "Mock infrastructure for AI application testing — LLM APIs, image generation, text-to-speech, transcription, video generation, MCP tools, A2A agents, AG-UI event streams, vector databases, search, rerank, and moderation. One package, one port, zero dependencies.",
0 commit comments