New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implements utils that can be used to ensure that all disposables are disposed after a test completes #127329
Conversation
…disposed after a test completes.
src/vs/base/common/lifecycle.ts
Outdated
* Creates a disposable that calls `fn` and then clears its reference to `fn` when being disposed. | ||
* This prevents memory leaks and ensures `fn` is called at most once. | ||
*/ | ||
export function toNonLeakingDisposable(fn: () => void): IDisposable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason why toDisposable
should not do the same self.dispose = noop
? i.e. shouldn't toDisposable
just be toNonLeakingDisposable
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would change the semantics of toDisposable
: Currently, multiple calls of dispose
will lead to multiple calls of fn
.
With self.dispose = noop
, multiple calls of dispose
will only call fn
once.
I don't know if anyone depends on this behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @joaomoreno is Mr toDisposable
but I agree that this should be called only once
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are all happy, optimistically pushed that: f8cf8f3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @joaomoreno is Mr
toDisposable
but I agree that this should be called only once
As if I would have any reason other than didn't think about that 🤷♂️.
Let's have toDisposable
do exactly what toNonLeakingDisposable
does.
* Clears the value, but does not dispose it. | ||
* The old value is returned. | ||
*/ | ||
clearAndLeak(): T | undefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could not find a usage of this. Is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed in inline completions. Currently they use a copy of MutableDisposable
that has this method, as I didn't want to be blocked by a code review.
Also, clearAndLeak
makes use of the new tracking API (disposables can be removed from parent disposables).
src/vs/base/common/async.ts
Outdated
@@ -19,18 +19,19 @@ export interface CancelablePromise<T> extends Promise<T> { | |||
} | |||
|
|||
export function createCancelablePromise<T>(callback: (token: CancellationToken) => Promise<T>): CancelablePromise<T> { | |||
const source = new CancellationTokenSource(); | |||
const disposableStore = new DisposableStore(); | |||
const source = disposableStore.add(new CancellationTokenSource()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why the source is wrapped in a dispo store? Is this to capture the listener from onCancellationRequested? This shouldn't be a problem when the store is always disposed. Btw, this made me realize that there is still a leak here: when cancellation happens (L26/27) the source/disposableStore isn't disposed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing out the leak!
Is this to capture the listener from onCancellationRequested?
Yes.
This shouldn't be a problem when the store is always disposed.
You are right, it does not need to be disposed here as the disposables inside of CancellationTokenSource
are not tracked.
It is very important to always dispose the event subscription though, otherwise the tooling introduced by this PR will recognize that as leak (even though garbage collection does take care of it).
# Conflicts: # src/vs/base/common/lifecycle.ts
907f92b
to
dffe01c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thanks for looking into this
This PR fixes #127250.
As part of this, some disposables that previously were automatically "disposed" by the garbage collector are now disposed explicitly.
Helpers are added to support tests in the following style:
This also applies to event subscriptions that now use
toSelfClearingDisposable
, which supports tracking.Use
markAsSingleton
to exclude disposable singletons (and all its registered children) that are created during the test.