fix: prevent DDPQueue unblock stack overflow via depth-based microtask breaking#11
fix: prevent DDPQueue unblock stack overflow via depth-based microtask breaking#11welkinwong wants to merge 1 commit into
Conversation
|
Hi @welkinwong , thanks for the PR and for digging into this! I took a look at the change and had a question — are you running the latest version of the agent that includes the I ask because the depth-based microtask approach has a subtle downside — |
|
@mvogttech Hi, thanks for the follow-up. I’ve installed the latest version (
Our project characteristic is that we call |
There was a problem hiding this comment.
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // retries. Once nesting gets too deep, defer one hop to break recursion. | ||
| if (self.unblockDepth >= 1000) { | ||
| queueMicrotask(() => { | ||
| originalUnblock(); |
There was a problem hiding this comment.
When originalUnblock() is deferred via queueMicrotask, any exception it throws will become an unhandled promise rejection (Node.js will emit an unhandledRejection or uncaughtException depending on the Node version), since there is no try/catch around the call inside the microtask. In the synchronous path, exceptions propagate to the caller. This inconsistency means that in the deferred path, errors from deep in the unblock chain will be silently lost or will crash the process, rather than being handled by the existing error-handling logic in the wrappers. A try/catch should be added inside the queueMicrotask callback to handle errors consistently with the synchronous path.
| originalUnblock(); | |
| try { | |
| originalUnblock(); | |
| } catch (error) { | |
| // Ensure errors in the deferred path are handled and don't surface | |
| // as unhandled rejections / uncaught exceptions. | |
| console.error('⚠️ DDPQueueCollector: Error during deferred unblock:', error); | |
| } |
|
Implemented in 9cd84f4 |
Summary
Fixed a stack overflow edge case in
DDPQueueCollector.wrapUnblock()where very deep synchronous unblock chains could triggerRangeError: Maximum call stack size exceeded.Added a depth guard with a hardcoded threshold (1000); once reached, the next unblock hop is deferred via
queueMicrotaskto break the current call stack safely.Added a regression test for deep chained unblock wrappers to verify no stack overflow and exactly-once execution of the base unblock.
Fixed #7
Test Plan
Run: