Fix memory leak from long-lived cancellation promise#168
Merged
Conversation
🦋 Changeset detectedLatest commit: 5954fbd The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
southpolesteve
approved these changes
May 7, 2026
Contributor
|
Thanks for the fix and taking over! And sorry about the delay in response, I’ve been busy past few weeks and couldn’t find the time to apply the requested changes. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This fix is actually #154 from @VastBlast, but I removed the middle commit and squashed the other two. (The middle commit of #154 was addressing an unrelated problem and is more controversial -- see my comments there.)
This fixes a memory leak in long sessions. It turns out that due to the implementation details of JavaScript promises:
Promise.race([a, b])many times and one of the promises (b, let's say) is always the same promise, then each call will add a new "reaction" callback tob, which never gets removed untilbactually fires. These reactions all become no-ops assumingaresolves first, but they still stick around.Promiserepresenting the result of the race, and thatPromiseobject itself, once resolved, holds a reference to its resolution. Hence, all of the resolutions of the differentapromises end up reachable fromband so cannot be GC'd.The net result for Cap'n Web is: All of the messages received on a WebSocket session remained stuck in-memory until the session closed. Whoops.
This is all very silly. One would expect that a reactor callback pointing to an already-resolved promise should simply "null itself out" so that the GC can see that the resolution is not reachable through it. But the problem seems to have arisen because:
Ironically, despite this problem existing for a decade already, it seems there is a recent push to fix it, at least in V8: https://chromium-review.googlesource.com/c/v8/v8/+/7646250
But this won't fix the
Promise.raceleak (first point above), so we should still land this fix to Cap'n WEb.