perf(core): fix OOM crash in long-running sessions#21207
perf(core): fix OOM crash in long-running sessions#21207
Conversation
Replace structuredClone() in getHistory() with a shallow array copy. The deep clone ran on every turn's hot path, duplicating the entire conversation history (50-100MB+ in long sessions) multiple times per turn. Verified all 8 production callers — only one mutated Content objects (nextSpeakerChecker), and that mutation was dead code (pushed to a clone that was immediately discarded). Replace Array.shift() in the event backlog with a head pointer and amortized compaction. shift() on a 10K-element array is O(n); the new approach is O(1) amortized with identical FIFO semantics. Cache Turn.getResponseText() result to avoid redundant recomputation across its two call sites per turn. Fixes #19607 fix(core): remove unknown cast for backlog
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the performance and memory efficiency of core chat functionalities. By transitioning from deep cloning to shallow copying for conversation history, it resolves critical OOM issues in extended sessions. Additionally, it optimizes event backlog management and introduces caching for response text retrieval, leading to a more robust and responsive user experience, especially in long-running conversations. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces significant performance improvements to address an out-of-memory crash in long-running sessions. Replacing the deep structuredClone() with a shallow array copy in getHistory() is an excellent optimization that directly targets the source of the memory pressure. The addition of caching to getResponseText() is also a good, clean optimization. I have one suggestion to enhance the type safety of the getHistory method, ensuring its contract is enforced at compile time to prevent potential state corruption issues.
| // Return a shallow copy of the array to prevent callers from mutating | ||
| // the internal history array (push/pop/splice). Content objects are | ||
| // shared references — callers MUST NOT mutate them in place. | ||
| // This replaces a prior structuredClone() which deep-copied the entire | ||
| // conversation on every call, causing O(n) memory pressure per turn | ||
| // that compounded into OOM crashes in long-running sessions. | ||
| return [...history]; |
There was a problem hiding this comment.
The move to a shallow copy is a great performance optimization that resolves the OOM issue. However, relying solely on a comment to prevent callers from mutating the shared Content objects is fragile and could lead to hard-to-debug state corruption down the line.
To enforce this contract at compile time without any runtime performance penalty, you can leverage TypeScript's Readonly types. This provides a much stronger guarantee against accidental mutations.
I recommend updating the function signature to return a ReadonlyArray<Content>:
getHistory(curated: boolean = false): ReadonlyArray<Content> {This change will prevent methods like .push() or direct index assignments on the returned array, making the implementation significantly safer.
getHistory(curated: boolean = false): ReadonlyArray<Content> {
Replace structuredClone() in getHistory() with a shallow array copy.
The deep clone ran on every turn's hot path, duplicating the entire
conversation history (50-100MB+ in long sessions) multiple times per
turn. Verified all 8 production callers — only one mutated Content
objects (nextSpeakerChecker), and that mutation was dead code (pushed
to a clone that was immediately discarded).
Replace Array.shift() in the event backlog with a head pointer and
amortized compaction. shift() on a 10K-element array is O(n); the
new approach is O(1) amortized with identical FIFO semantics.
Cache Turn.getResponseText() result to avoid redundant recomputation
across its two call sites per turn.
Fixes #19607
fix(core): remove unknown cast for backlog
Summary
Details
Related Issues
How to Validate
Pre-Merge Checklist