Services: Fix memory leaks in watch mode - #504
Merged
Merged
Conversation
justinfagnani
approved these changes
Nov 4, 2022
| } | ||
| await new Promise((resolve) => setTimeout(resolve, wait)); | ||
| } | ||
| cb(); |
Member
Author
There was a problem hiding this comment.
Added a comment. It's the attempt after the final wait, where we don't wrap in a try so that the exception bubbles up.
| } | ||
| } | ||
|
|
||
| await retryWithGcUntilCallbackDoesNotThrow(() => { |
There was a problem hiding this comment.
how often does this need all the retries to pass?
Member
Author
There was a problem hiding this comment.
In a single run I just did, one time needed 10, another needed 100.
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.
Fixes two memory leaks that affected watch mode:
An
abortpromise which always remained unresolved until wireit exited. This prevented both standard and service executions from ever being garbage collected, because they both awaited those promises indefinitely, even after those instances were stale.The fix was to replace the promise entirely with explicit
abort()methods, which turned out to be simpler and less error-prone anyway.The
Executorfor watch mode iteration N was holding references to all persistent services from watch mode iteration N-1, and so on all the way back through all iterations, because of thepreviousIterationServicesmap that we pass forward across iterations.The fix was to delete services from the map after we know they are adopted, breaking the reference chain.
This PR includes new garbage collection tests that use
FinalizationRegistryto keep track of how many instances ofExecutorsandExecutionsthere are after aglobal.gc(). I'm just running that on a single OS/node version on CI, since I think it might be slightly flaky, plus I think that's good enough anyway.In writing the GC tests, I also found a few other bugs with services -- some unhandled state transitions, and a timing issue where we weren't waiting for persistent services to be ready before indicating that the first phase of execution was done.
Part of #33