Skip to content

Commit cdf3947

Browse files
committed
Adds cancellation support to Graph refresh
1 parent 1425bc7 commit cdf3947

File tree

2 files changed

+130
-39
lines changed

2 files changed

+130
-39
lines changed

src/webviews/plus/graph/graphWebview.ts

Lines changed: 107 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ import { isDarkTheme, isLightTheme } from '../../../system/-webview/vscode';
113113
import { openUrl } from '../../../system/-webview/vscode/uris';
114114
import type { OpenWorkspaceLocation } from '../../../system/-webview/vscode/workspaces';
115115
import { openWorkspace } from '../../../system/-webview/vscode/workspaces';
116-
import { gate } from '../../../system/decorators/-webview/gate';
117116
import { debug, log } from '../../../system/decorators/log';
118117
import { disposableInterval } from '../../../system/function';
119118
import type { Deferrable } from '../../../system/function/debounce';
@@ -247,7 +246,7 @@ const compactGraphColumnsSettings: GraphColumnsSettings = {
247246
sha: { width: 130, isHidden: false, order: 6 },
248247
};
249248

250-
type CancellableOperations = 'hover' | 'computeIncludedRefs' | 'search' | 'state';
249+
type CancellableOperations = 'branchState' | 'hover' | 'computeIncludedRefs' | 'search' | 'state';
251250

252251
export class GraphWebviewProvider implements WebviewProvider<State, State, GraphWebviewShowingArgs> {
253252
private _repository?: Repository;
@@ -1473,7 +1472,6 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
14731472
this.updateRefsMetadata();
14741473
}
14751474

1476-
@gate()
14771475
@debug()
14781476
private async onGetMoreRows(e: GetMoreRowsParams, sendSelectedRows: boolean = false) {
14791477
if (this._graph?.paging == null) return;
@@ -1483,13 +1481,7 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
14831481
return;
14841482
}
14851483

1486-
using sw = new Stopwatch(`GraohWebviewProvider.onGetMoreRows(${this.host.id})`);
14871484
await this.updateGraphWithMoreRows(this._graph, e.id, this._search);
1488-
this.container.telemetry.sendEvent('graph/rows/loaded', {
1489-
...this.getTelemetryContext(),
1490-
duration: sw.elapsed(),
1491-
rows: this._graph.rows.length ?? 0,
1492-
});
14931485
void this.notifyDidChangeRows(sendSelectedRows);
14941486
}
14951487

@@ -2201,14 +2193,18 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
22012193
const cancellation = this.createCancellation('computeIncludedRefs');
22022194

22032195
const [baseResult, defaultResult, targetResult] = await Promise.allSettled([
2204-
this.container.git.branches(current.repoPath).getBaseBranchName?.(current.name),
2205-
getDefaultBranchName(this.container, current.repoPath, current.getRemoteName()),
2196+
this.container.git.branches(current.repoPath).getBaseBranchName?.(current.name, cancellation.token),
2197+
getDefaultBranchName(this.container, current.repoPath, current.getRemoteName(), {
2198+
cancellation: cancellation.token,
2199+
}),
22062200
getTargetBranchName(this.container, current, {
22072201
cancellation: cancellation.token,
22082202
timeout: options?.timeout,
22092203
}),
22102204
]);
22112205

2206+
if (cancellation.token.isCancellationRequested) return { refs: {} };
2207+
22122208
const baseBranchName = getSettledValue(baseResult);
22132209
const defaultBranchName = getSettledValue(defaultResult);
22142210
const targetMaybeResult = getSettledValue(targetResult);
@@ -2467,13 +2463,13 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
24672463
return item;
24682464
}
24692465

2470-
private async getWorkingTreeStats(): Promise<GraphWorkingTreeStats | undefined> {
2466+
private async getWorkingTreeStats(cancellation?: CancellationToken): Promise<GraphWorkingTreeStats | undefined> {
24712467
if (this.repository == null || this.container.git.repositoryCount === 0) return undefined;
24722468

2473-
const statusProivder = this.container.git.status(this.repository.path);
2474-
const status = await statusProivder.getStatus();
2469+
const statusProvider = this.container.git.status(this.repository.path);
2470+
const status = await statusProvider.getStatus(cancellation);
24752471
const workingTreeStatus = status?.getDiffStatus();
2476-
const pausedOpStatus = await statusProivder.getPausedOperationStatus?.();
2472+
const pausedOpStatus = await statusProvider.getPausedOperationStatus?.(cancellation);
24772473

24782474
return {
24792475
added: workingTreeStatus?.added ?? 0,
@@ -2492,6 +2488,9 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
24922488
}
24932489

24942490
private async getState(deferRows?: boolean): Promise<State> {
2491+
this.cancelOperation('branchState');
2492+
this.cancelOperation('state');
2493+
24952494
if (this.container.git.repositoryCount === 0) {
24962495
return { ...this.host.baseWebviewState, allowed: true, repositories: [] };
24972496
}
@@ -2503,6 +2502,8 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
25032502
}
25042503
}
25052504

2505+
const cancellation = this.createCancellation('state');
2506+
25062507
this._etagRepository = this.repository?.etag;
25072508
this.host.title = `${this.host.originalTitle}: ${this.repository.formattedName}`;
25082509

@@ -2517,35 +2518,42 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
25172518
const columns = this.getColumns();
25182519
const columnSettings = this.getColumnSettings(columns);
25192520

2520-
const dataPromise = this.repository.git.graph().getGraph(rev, uri => this.host.asWebviewUri(uri), {
2521-
include: {
2522-
stats:
2523-
(configuration.get('graph.minimap.enabled') &&
2524-
configuration.get('graph.minimap.dataType') === 'lines') ||
2525-
!columnSettings.changes.isHidden,
2521+
const dataPromise = this.repository.git.graph().getGraph(
2522+
rev,
2523+
uri => this.host.asWebviewUri(uri),
2524+
{
2525+
include: {
2526+
stats:
2527+
(configuration.get('graph.minimap.enabled') &&
2528+
configuration.get('graph.minimap.dataType') === 'lines') ||
2529+
!columnSettings.changes.isHidden,
2530+
},
2531+
limit: limit,
25262532
},
2527-
limit: limit,
2528-
});
2533+
cancellation.token,
2534+
);
25292535

25302536
// Check for access and working tree stats
25312537
const promises = Promise.allSettled([
25322538
this.getGraphAccess(),
2533-
this.getWorkingTreeStats(),
2534-
this.repository.git.branches().getBranch(),
2539+
this.getWorkingTreeStats(cancellation.token),
2540+
this.repository.git.branches().getBranch(undefined, cancellation.token),
25352541
this.repository.getLastFetched(),
25362542
]);
25372543

25382544
let data;
25392545
if (deferRows) {
25402546
queueMicrotask(async () => {
2541-
const data = await dataPromise;
2542-
this.setGraph(data);
2543-
if (selectedId !== uncommitted) {
2544-
this.setSelectedRows(data.id);
2545-
}
2547+
try {
2548+
const data = await dataPromise;
2549+
this.setGraph(data);
2550+
if (selectedId !== uncommitted) {
2551+
this.setSelectedRows(data.id);
2552+
}
25462553

2547-
void this.notifyDidChangeRefsVisibility();
2548-
void this.notifyDidChangeRows(true);
2554+
void this.notifyDidChangeRefsVisibility();
2555+
void this.notifyDidChangeRows(true);
2556+
} catch {}
25492557
});
25502558
} else {
25512559
data = await dataPromise;
@@ -2556,6 +2564,8 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
25562564
}
25572565

25582566
const [accessResult, workingStatsResult, branchResult, lastFetchedResult] = await promises;
2567+
if (cancellation.token.isCancellationRequested) throw new CancellationError();
2568+
25592569
const [access, visibility] = getSettledValue(accessResult) ?? [];
25602570

25612571
let branchState: BranchState | undefined;
@@ -2564,17 +2574,18 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
25642574
if (branch != null) {
25652575
branchState = { ...(branch.upstream?.state ?? { ahead: 0, behind: 0 }) };
25662576

2567-
const worktreesByBranch = data?.worktreesByBranch ?? (await getWorktreesByBranch(this.repository));
2577+
const worktreesByBranch =
2578+
data?.worktreesByBranch ?? (await getWorktreesByBranch(this.repository, undefined, cancellation.token));
25682579
branchState.worktree = worktreesByBranch?.has(branch.id) ?? false;
25692580

25702581
if (branch.upstream != null) {
25712582
branchState.upstream = branch.upstream.name;
25722583

2573-
const cancellation = this.createCancellation('state');
2584+
const branchStateCancellation = this.createCancellation('branchState');
25742585

25752586
const [remoteResult, prResult] = await Promise.allSettled([
25762587
branch.getRemote(),
2577-
pauseOnCancelOrTimeout(branch.getAssociatedPullRequest(), cancellation.token, 100),
2588+
pauseOnCancelOrTimeout(branch.getAssociatedPullRequest(), branchStateCancellation.token, 100),
25782589
]);
25792590

25802591
const remote = getSettledValue(remoteResult);
@@ -2590,7 +2601,7 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
25902601
if (maybePr?.paused) {
25912602
const updatedBranchState = { ...branchState };
25922603
void maybePr.value.then(pr => {
2593-
if (cancellation?.token.isCancellationRequested) return;
2604+
if (branchStateCancellation?.token.isCancellationRequested) return;
25942605

25952606
if (pr != null) {
25962607
updatedBranchState.pr = serializePullRequest(pr);
@@ -3044,9 +3055,68 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
30443055
}
30453056
}
30463057

3058+
private _pendingRowsQuery:
3059+
| {
3060+
promise: Promise<void>;
3061+
cancellable: CancellationTokenSource;
3062+
id?: string | undefined;
3063+
search?: GitGraphSearch;
3064+
}
3065+
| undefined;
30473066
private async updateGraphWithMoreRows(graph: GitGraph, id: string | undefined, search?: GitGraphSearch) {
3067+
if (this._pendingRowsQuery != null) {
3068+
const { id: pendingId, search: pendingSearch } = this._pendingRowsQuery;
3069+
if (pendingSearch === search && (pendingId === id || (pendingId != null && id == null))) {
3070+
return this._pendingRowsQuery.promise;
3071+
}
3072+
3073+
this._pendingRowsQuery.cancellable.cancel();
3074+
this._pendingRowsQuery.cancellable.dispose();
3075+
this._pendingRowsQuery = undefined;
3076+
}
3077+
3078+
const sw = new Stopwatch(undefined);
3079+
3080+
const cancellable = new CancellationTokenSource();
3081+
const cancellation = cancellable.token;
3082+
3083+
this._pendingRowsQuery = {
3084+
promise: this.updateGraphWithMoreRowsCore(graph, id, search, cancellation).catch((ex: unknown) => {
3085+
if (cancellation.isCancellationRequested) return;
3086+
3087+
throw ex;
3088+
}),
3089+
cancellable: cancellable,
3090+
id: id,
3091+
search: search,
3092+
};
3093+
3094+
void this._pendingRowsQuery.promise.finally(() => {
3095+
if (cancellation.isCancellationRequested) return;
3096+
3097+
this.container.telemetry.sendEvent('graph/rows/loaded', {
3098+
...this.getTelemetryContext(),
3099+
duration: sw.elapsed(),
3100+
rows: graph.rows.length ?? 0,
3101+
});
3102+
sw.stop();
3103+
3104+
this._pendingRowsQuery = undefined;
3105+
});
3106+
3107+
return this._pendingRowsQuery.promise;
3108+
}
3109+
3110+
private async updateGraphWithMoreRowsCore(
3111+
graph: GitGraph,
3112+
id: string | undefined,
3113+
search?: GitGraphSearch,
3114+
cancellation?: CancellationToken,
3115+
) {
3116+
console.warn('##### updateGraphWithMoreRows', id, search);
3117+
30483118
const { defaultItemLimit, pageItemLimit } = configuration.get('graph');
3049-
const updatedGraph = await graph.more?.(pageItemLimit ?? defaultItemLimit, id);
3119+
const updatedGraph = await graph.more?.(pageItemLimit ?? defaultItemLimit, id ?? undefined, cancellation);
30503120
if (updatedGraph != null) {
30513121
this.setGraph(updatedGraph);
30523122

src/webviews/webviewController.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { WebviewCommands, WebviewViewCommands } from '../constants.commands
55
import type { WebviewTelemetryContext } from '../constants.telemetry';
66
import type { CustomEditorTypes, WebviewIds, WebviewTypes, WebviewViewIds, WebviewViewTypes } from '../constants.views';
77
import type { Container } from '../container';
8+
import { isCancellationError } from '../errors';
89
import { executeCommand, executeCoreCommand } from '../system/-webview/command';
910
import { setContext } from '../system/-webview/context';
1011
import { getScopedCounter } from '../system/counter';
@@ -363,7 +364,16 @@ export class WebviewController<
363364

364365
if (loading) {
365366
this.cancellation ??= new CancellationTokenSource();
366-
this.webview.html = await this.getHtml(this.webview);
367+
try {
368+
this.webview.html = await this.getHtml(this.webview);
369+
} catch (ex) {
370+
if (isCancellationError(ex)) {
371+
this.cancellation.cancel();
372+
return;
373+
}
374+
375+
throw ex;
376+
}
367377
}
368378

369379
if (this.isHost('editor')) {
@@ -420,7 +430,18 @@ export class WebviewController<
420430
const wasReady = this._ready;
421431
this._ready = false;
422432

423-
const html = await this.getHtml(this.webview);
433+
let html;
434+
try {
435+
html = await this.getHtml(this.webview);
436+
} catch (ex) {
437+
if (isCancellationError(ex)) {
438+
this.cancellation.cancel();
439+
return;
440+
}
441+
442+
throw ex;
443+
}
444+
424445
if (force) {
425446
// Reset the html to get the webview to reload
426447
this.webview.html = '';

0 commit comments

Comments
 (0)