Repository navigation
fix: encodeViaTurboStream leaks memory via unremoved AbortSignal listener - #14891
luchsamapparat wants to merge 1 commit into
Conversation
|
|
Hi @luchsamapparat, Welcome, and thank you for contributing to React Router! Before we consider your pull request, we ask that you sign our Contributor License Agreement (CLA). We require this only once. You may review the CLA and sign it by adding your name to contributors.yml. Once the CLA is signed, the If you have already signed the CLA and received this response in error, or if you have any questions, please contact us at hello@remix.run. Thanks! - The Remix team |
|
Thank you for signing the Contributor License Agreement. Let's get this merged! 🥳 |
encodeViaTurboStream leaks memory via unremoved AbortSignal listenerencodeViaTurboStream leaks memory via unremoved AbortSignal listener
|
I've re-targeted this at dev and forwarded the request abort signal through at #14900 |
encodeViaTurboStreamregisters aclearStreamTimeoutlistener onrequestSignalbut never removes it. Even though #14735 correctly addedonComplete: clearStreamTimeoutto clear the timer when encoding finishes, the listener itself remains attached to theAbortSignalfor the lifetime of theRequestobject.Because the
clearStreamTimeoutclosure capturestimeoutId, theTimeoutobject (and everything it transitively retains) cannot be GC'd until theRequestis eventually collected. Under sustained request load this causes unbounded accumulation of liveTimeoutobjects, like in this heap snapshot:In our case, we use
AsyncLocalStorageto scope per-request state. Because of this leak, eachTimeoutretains its fullAsyncContextFrameand with it the entire ALS store associated with the request.The above screenshot is from running 100 E2E tests against our application.
This is a heap snapshot after the same 100 E2E tests after applying the fix: