Skip to content

Commit d894a16

Browse files
jeanp413bpasero
andauthored
Fix timeline view leaks event listeners (microsoft#241607)
* Fixes microsoft#241536 * Feedback * cleanup * fix hygieene --------- Co-authored-by: Benjamin Pasero <benjamin.pasero@gmail.com>
1 parent 71714b7 commit d894a16

File tree

2 files changed

+44
-31
lines changed

2 files changed

+44
-31
lines changed

src/vs/workbench/contrib/timeline/browser/timelinePane.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ export const TimelineFollowActiveEditorContext = new RawContextKey<boolean>('tim
235235
export const TimelineExcludeSources = new RawContextKey<string>('timelineExcludeSources', '[]', true);
236236
export const TimelineViewFocusedContext = new RawContextKey<boolean>('timelineFocused', true);
237237

238+
interface IPendingRequest extends IDisposable {
239+
readonly request: TimelineRequest;
240+
}
241+
238242
export class TimelinePane extends ViewPane {
239243
static readonly TITLE: ILocalizedString = localize2('timeline', "Timeline");
240244

@@ -250,7 +254,7 @@ export class TimelinePane extends ViewPane {
250254
private timelineExcludeSourcesContext: IContextKey<string>;
251255

252256
private excludedSources: Set<string>;
253-
private pendingRequests = new Map<string, TimelineRequest>();
257+
private pendingRequests = new Map<string, IPendingRequest>();
254258
private timelinesBySource = new Map<string, TimelineAggregate>();
255259

256260
private uri: URI | undefined;
@@ -495,8 +499,9 @@ export class TimelinePane extends ViewPane {
495499
this.timelinesBySource.clear();
496500

497501
if (cancelPending) {
498-
for (const { tokenSource } of this.pendingRequests.values()) {
499-
tokenSource.dispose(true);
502+
for (const pendingRequest of this.pendingRequests.values()) {
503+
pendingRequest.request.tokenSource.cancel();
504+
pendingRequest.dispose();
500505
}
501506

502507
this.pendingRequests.clear();
@@ -588,34 +593,38 @@ export class TimelinePane extends ViewPane {
588593
options = { cursor: reset ? undefined : timeline?.cursor, limit: this.pageSize };
589594
}
590595

591-
let request = this.pendingRequests.get(source);
592-
if (request !== undefined) {
593-
options.cursor = request.options.cursor;
596+
const pendingRequest = this.pendingRequests.get(source);
597+
if (pendingRequest !== undefined) {
598+
options.cursor = pendingRequest.request.options.cursor;
594599

595600
// TODO@eamodio deal with concurrent requests better
596601
if (typeof options.limit === 'number') {
597-
if (typeof request.options.limit === 'number') {
598-
options.limit += request.options.limit;
602+
if (typeof pendingRequest.request.options.limit === 'number') {
603+
options.limit += pendingRequest.request.options.limit;
599604
} else {
600-
options.limit = request.options.limit;
605+
options.limit = pendingRequest.request.options.limit;
601606
}
602607
}
603608
}
604-
request?.tokenSource.dispose(true);
609+
pendingRequest?.request?.tokenSource.cancel();
610+
pendingRequest?.dispose();
611+
605612
options.cacheResults = true;
606613
options.resetCache = reset;
607-
request = this.timelineService.getTimeline(
608-
source, uri, options, new CancellationTokenSource()
609-
);
614+
const tokenSource = new CancellationTokenSource();
615+
const newRequest = this.timelineService.getTimeline(source, uri, options, tokenSource);
610616

611-
if (request === undefined) {
617+
if (newRequest === undefined) {
618+
tokenSource.dispose();
612619
return false;
613620
}
614621

615-
this.pendingRequests.set(source, request);
616-
request.tokenSource.token.onCancellationRequested(() => this.pendingRequests.delete(source));
622+
const disposables = new DisposableStore();
623+
this.pendingRequests.set(source, { request: newRequest, dispose: () => disposables.dispose() });
624+
disposables.add(tokenSource);
625+
disposables.add(tokenSource.token.onCancellationRequested(() => this.pendingRequests.delete(source)));
617626

618-
this.handleRequest(request);
627+
this.handleRequest(newRequest);
619628

620629
return true;
621630
}
@@ -641,6 +650,7 @@ export class TimelinePane extends ViewPane {
641650
response = await this.progressService.withProgress({ location: this.id }, () => request.result);
642651
}
643652
finally {
653+
this.pendingRequests.get(request.source)?.dispose();
644654
this.pendingRequests.delete(request.source);
645655
}
646656

@@ -920,11 +930,11 @@ export class TimelinePane extends ViewPane {
920930
container.appendChild(this.$tree);
921931

922932
this.treeRenderer = this.instantiationService.createInstance(TimelineTreeRenderer, this.commands);
923-
this.treeRenderer.onDidScrollToEnd(item => {
933+
this._register(this.treeRenderer.onDidScrollToEnd(item => {
924934
if (this.pageOnScroll) {
925935
this.loadMore(item);
926936
}
927-
});
937+
}));
928938

929939
this.tree = this.instantiationService.createInstance(WorkbenchObjectTree<TreeElement, FuzzyScore>, 'TimelinePane',
930940
this.$tree, new TimelineListVirtualDelegate(), [this.treeRenderer], {

src/vs/workbench/contrib/timeline/common/timelineService.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
7-
import { Event, Emitter } from '../../../../base/common/event.js';
8-
import { IDisposable } from '../../../../base/common/lifecycle.js';
7+
import { Emitter } from '../../../../base/common/event.js';
8+
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
99
import { URI } from '../../../../base/common/uri.js';
1010
import { ILogService } from '../../../../platform/log/common/log.js';
1111
import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider, TimelinePaneId } from './timeline.js';
@@ -15,16 +15,16 @@ import { IContextKey, IContextKeyService, RawContextKey } from '../../../../plat
1515

1616
export const TimelineHasProviderContext = new RawContextKey<boolean>('timelineHasProvider', false);
1717

18-
export class TimelineService implements ITimelineService {
18+
export class TimelineService extends Disposable implements ITimelineService {
1919
declare readonly _serviceBrand: undefined;
2020

21-
private readonly _onDidChangeProviders = new Emitter<TimelineProvidersChangeEvent>();
22-
readonly onDidChangeProviders: Event<TimelineProvidersChangeEvent> = this._onDidChangeProviders.event;
21+
private readonly _onDidChangeProviders = this._register(new Emitter<TimelineProvidersChangeEvent>());
22+
readonly onDidChangeProviders = this._onDidChangeProviders.event;
2323

24-
private readonly _onDidChangeTimeline = new Emitter<TimelineChangeEvent>();
25-
readonly onDidChangeTimeline: Event<TimelineChangeEvent> = this._onDidChangeTimeline.event;
26-
private readonly _onDidChangeUri = new Emitter<URI>();
27-
readonly onDidChangeUri: Event<URI> = this._onDidChangeUri.event;
24+
private readonly _onDidChangeTimeline = this._register(new Emitter<TimelineChangeEvent>());
25+
readonly onDidChangeTimeline = this._onDidChangeTimeline.event;
26+
private readonly _onDidChangeUri = this._register(new Emitter<URI>());
27+
readonly onDidChangeUri = this._onDidChangeUri.event;
2828

2929
private readonly hasProviderContext: IContextKey<boolean>;
3030
private readonly providers = new Map<string, TimelineProvider>();
@@ -36,6 +36,8 @@ export class TimelineService implements ITimelineService {
3636
@IConfigurationService protected configurationService: IConfigurationService,
3737
@IContextKeyService protected contextKeyService: IContextKeyService,
3838
) {
39+
super();
40+
3941
this.hasProviderContext = TimelineHasProviderContext.bindTo(this.contextKeyService);
4042
this.updateHasProviderContext();
4143
}
@@ -72,10 +74,10 @@ export class TimelineService implements ITimelineService {
7274

7375
return result;
7476
}),
75-
options: options,
77+
options,
7678
source: provider.id,
77-
tokenSource: tokenSource,
78-
uri: uri
79+
tokenSource,
80+
uri
7981
};
8082
}
8183

@@ -120,6 +122,7 @@ export class TimelineService implements ITimelineService {
120122
}
121123

122124
this.providers.delete(id);
125+
this.providerSubscriptions.get(id)?.dispose();
123126
this.providerSubscriptions.delete(id);
124127

125128
this.updateHasProviderContext();

0 commit comments

Comments
 (0)