Check for isolate termination in iterating C++ callbacks#6331
Check for isolate termination in iterating C++ callbacks#6331
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
|
error: Your local changes to the following files would be overwritten by checkout: |
|
@erikcorry Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
43ca90e to
b5e9d6f
Compare
Root cause: When JS code uses Array.from() or spread on a C++ iterator (like the SQL cursor), V8's Torque builtin loops over .next() calls without JS back-edge interrupt checks. If TerminateExecution() is called (e.g. by the near-heap-limit callback when memory is exceeded), the termination request sits unprocessed in the stack guard, and the iterator keeps running, growing the heap until it hits the 4GB pointer compression cage limit and crashes. Three changes: 1. deps/workerd/src/workerd/jsg/setup.h -- Added requestTermination() / isTerminationRequested() flag on IsolateBase, checkable from C++ callbacks without relying on V8's internal interrupt processing. 2. deps/workerd/src/workerd/jsg/iterator.h + iterator.c++ -- nextImpl() checks isTerminationRequested() after each successful row and throws a JS exception if set. V8 sees the exception on the callback return path and stops the iteration. 3. src/edgeworker/scheduling/limit-enforcer-impl.c++ -- terminate() now calls requestTermination() alongside TerminateExecution(). Bug: EW-10621
bd280a8 to
bcba1b6
Compare
| // Signals that execution should be terminated immediately, including during C++ iterator | ||
| // callbacks where V8's own TerminateExecution() interrupt would not be checked. | ||
| // Also calls V8's TerminateExecution(). See IsolateBase::requestTermination(). | ||
| void requestTermination(); |
There was a problem hiding this comment.
I think you need to make this method thread-safe. It looks like you've arranged for it to be called from many places where we may be terminating from another thread. So this needs to be const and probably the terminationRequested boolean needs to be an atomic.
(Though don't think there's any problem in practice since it's just a boolean...)
Root cause: When JS code uses Array.from() or spread on a C++ iterator (like the SQL cursor), V8's Torque builtin loops over .next() calls without JS back-edge interrupt checks. If TerminateExecution() is called (e.g. by the near-heap-limit callback when memory is exceeded), the termination request sits unprocessed in the stack guard, and the iterator keeps running, growing the heap until it hits the 4GB pointer compression cage limit and crashes.
Three changes:
deps/workerd/src/workerd/jsg/setup.h -- Added requestTermination() / isTerminationRequested() flag on IsolateBase, checkable from C++ callbacks without relying on V8's internal interrupt processing.
deps/workerd/src/workerd/jsg/iterator.h + iterator.c++ -- nextImpl() checks isTerminationRequested() after each successful row and throws a JS exception if set. V8 sees the exception on the callback return path and stops the iteration.
src/edgeworker/scheduling/limit-enforcer-impl.c++ -- terminate() now calls requestTermination() alongside TerminateExecution().
Bug: EW-10621