-
Notifications
You must be signed in to change notification settings - Fork 3k
fix: prevent memory exhaustion in loops with bounded iteration outputs #2527
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@aadamsx is attempting to deploy a commit to the Sim Team on Vercel. A member of the Team first needs to authorize it. |
Greptile Summaryaddresses memory exhaustion in loops by adding configurable limits for iteration outputs (100 iterations, 50MB) and block logs (500 logs, 100MB). The implementation adds memory-bounded storage with count and size limits that trigger truncation when exceeded. Critical Issues Found:
Trade-offs:
Confidence Score: 1/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant LE as Loop Execution
participant LO as LoopOrchestrator
participant Scope as LoopScope
participant BE as BlockExecutor
participant Ctx as ExecutionContext
Note over LE,Ctx: Loop Iteration Flow with Memory Management
LE->>LO: evaluateLoopContinuation(ctx, loopId)
LO->>Scope: collect currentIterationOutputs
LO->>LO: addIterationOutputsWithMemoryLimit(scope, results, loopId)
Note over LO: Check count limit (100)
alt allIterationOutputs.length > MAX_STORED_ITERATION_OUTPUTS
LO->>Scope: slice() - discard oldest iterations
LO->>LO: logger.warn() - log truncation
end
Note over LO: Check memory size (50MB)
LO->>LO: estimateObjectSize(allIterationOutputs)
Note right of LO: JSON.stringify() runs EVERY iteration<br/>(performance issue)
alt estimatedSize > MAX_ITERATION_OUTPUTS_SIZE_BYTES
LO->>Scope: slice() - discard oldest half
LO->>LO: logger.warn() - log truncation
end
LO->>Scope: currentIterationOutputs.clear()
LO-->>LE: continuation result
Note over LE,Ctx: Block Execution Flow with Memory Management
LE->>BE: execute(ctx, node, block)
BE->>BE: createBlockLog(ctx, node.id, block, node)
BE->>BE: addBlockLogWithMemoryLimit(ctx, blockLog)
BE->>Ctx: blockLogs.push(blockLog)
Note over BE: Check count limit (500)
alt blockLogs.length > MAX_BLOCK_LOGS
BE->>Ctx: blockLogs = blockLogs.slice(discardCount)
BE->>BE: logger.warn() - log truncation
end
Note over BE: Periodic size check (every 50 logs)
alt blockLogs.length % 50 === 0
BE->>BE: estimateBlockLogsSize(blockLogs)
Note right of BE: JSON.stringify() every 50 logs
alt estimatedSize > MAX_BLOCK_LOGS_SIZE_BYTES
BE->>Ctx: blockLogs = blockLogs.slice(discardCount)
BE->>BE: logger.warn() - log truncation
end
end
BE->>BE: execute block handler
BE-->>LE: normalized output
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional Comments (5)
-
apps/sim/executor/orchestrators/loop.ts, line 519-529 (link)logic:
JSON.stringify()runs on EVERY iteration before size check, defeating the purpose of memory optimization. With 1000 iterations, this serializes potentially GBs of data 1000 times.Move size check to only run periodically (e.g., every 10 iterations) like
block-executor.ts:Then in
addIterationOutputsWithMemoryLimitat line 497:// Check memory size limit periodically (every 10 iterations to avoid frequent serialization) if (scope.allIterationOutputs.length % 10 === 0) { const estimatedSize = this.estimateObjectSize(scope.allIterationOutputs) if (estimatedSize > DEFAULTS.MAX_ITERATION_OUTPUTS_SIZE_BYTES) {
-
apps/sim/executor/execution/block-executor.ts, line 704-710 (link)logic: returns max limit on error, which prevents cleanup and allows unbounded growth if serialization consistently fails
-
apps/sim/executor/orchestrators/loop.ts, line 519-529 (link)logic: same issue - returning max limit prevents cleanup on serialization errors
-
apps/sim/executor/orchestrators/loop.ts, line 483-494 (link)logic: slicing from
discardCountremoves NEWEST iterations instead of oldest. The intent is to keep the most recent 100 iterations. -
apps/sim/executor/execution/block-executor.ts, line 673-682 (link)logic: slicing from
discardCountremoves NEWEST logs instead of oldest
3 files reviewed, 5 comments
This change addresses memory exhaustion issues that occur when running workflows with loops containing agent blocks that make many tool calls. Problem: Memory accumulated unbounded in two key areas: 1. allIterationOutputs in LoopScope - every iteration pushed results 2. blockLogs in ExecutionContext - every block execution added logs Solution: Added memory management with configurable limits in constants.ts: - MAX_STORED_ITERATION_OUTPUTS (100) and MAX_ITERATION_OUTPUTS_SIZE_BYTES (50MB) - MAX_BLOCK_LOGS (500) and MAX_BLOCK_LOGS_SIZE_BYTES (100MB) Loop orchestrator (loop.ts): - New addIterationOutputsWithMemoryLimit() method - Periodic size checks (every 10 iterations) to avoid serialization overhead - Discards oldest iterations when limits exceeded Block executor (block-executor.ts): - New addBlockLogWithMemoryLimit() method - Periodic size checks (every 50 logs) - Discards oldest logs when limits exceeded Trade-offs: - Final aggregated results contain only recent iterations - Logs show warning when truncation occurs for debugging Fixes simstudioai#2525
36bbe99 to
387efb6
Compare
Summary
This PR addresses memory exhaustion issues that occur when running workflows with loops containing agent blocks that make many tool calls (e.g., MCP file operations).
Fixes #2525
Problem
Memory accumulated unbounded in two key areas during workflow execution:
allIterationOutputsin LoopScope - every loop iteration pushed results to this array with no limitblockLogsin ExecutionContext - every block execution added logs with no pruningThis caused OOM crashes on systems with 64GB+ RAM during long-running workflow executions with loops.
Solution
Added memory management with configurable limits in two places:
Loop Orchestrator (
apps/sim/executor/orchestrators/loop.ts)addIterationOutputsWithMemoryLimit()methodMAX_STORED_ITERATION_OUTPUTS(default: 100)MAX_ITERATION_OUTPUTS_SIZE_BYTES(default: 50MB)Block Executor (
apps/sim/executor/execution/block-executor.ts)addBlockLogWithMemoryLimit()methodMAX_BLOCK_LOGS(default: 500)MAX_BLOCK_LOGS_SIZE_BYTES(default: 100MB)New Constants (
apps/sim/executor/constants.ts)MAX_STORED_ITERATION_OUTPUTS: 100MAX_ITERATION_OUTPUTS_SIZE_BYTES: 50MBMAX_BLOCK_LOGS: 500MAX_BLOCK_LOGS_SIZE_BYTES: 100MBTrade-offs
loop.resultswill contain only the most recent iterations (up to 100)Testing
Files Changed
apps/sim/executor/constants.ts- Added new configurable limitsapps/sim/executor/orchestrators/loop.ts- Added memory-bounded iteration storageapps/sim/executor/execution/block-executor.ts- Added memory-bounded log storage