Skip to content

Services: Fix memory leaks in watch mode - #504

Merged
aomarks merged 10 commits into
servicefrom
service-gc
Nov 4, 2022
Merged

Services: Fix memory leaks in watch mode#504
aomarks merged 10 commits into
servicefrom
service-gc

Conversation

@aomarks

@aomarks aomarks commented Nov 3, 2022

Copy link
Copy Markdown
Member

Fixes two memory leaks that affected watch mode:

  1. An abort promise 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.

  2. The Executor for 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 the previousIterationServices map 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 FinalizationRegistry to keep track of how many instances of Executors and Executions there are after a global.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

@aomarks
aomarks requested a review from justinfagnani November 3, 2022 19:48
@aomarks aomarks changed the title Services: Fix memory leak in watch mode for standard scripts Services: Fix memory leaks in watch mode Nov 4, 2022
Comment thread src/test/gc.test.ts
}
await new Promise((resolve) => setTimeout(resolve, wait));
}
cb();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this extra call?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/test/gc.test.ts
}
}

await retryWithGcUntilCallbackDoesNotThrow(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how often does this need all the retries to pass?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a single run I just did, one time needed 10, another needed 100.

@aomarks
aomarks merged commit f593a51 into service Nov 4, 2022
@aomarks
aomarks deleted the service-gc branch November 4, 2022 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants