Skip to content

Commit d30e58b

Browse files
Merge pull request #2453 from github/robertbrignull/refresh-promise
Add ability to await the discovery refresh promise
2 parents 8badc1c + a0e6317 commit d30e58b

7 files changed

Lines changed: 100 additions & 104 deletions

File tree

extensions/ql-vscode/src/common/discovery.ts

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,28 @@ import { getErrorMessage } from "../pure/helpers-pure";
88
* same time.
99
*/
1010
export abstract class Discovery<T> extends DisposableObject {
11-
private retry = false;
12-
private discoveryInProgress = false;
11+
private restartWhenFinished = false;
12+
private currentDiscoveryPromise: Promise<void> | undefined;
1313

1414
constructor(private readonly name: string) {
1515
super();
1616
}
1717

18+
/**
19+
* Returns the promise of the currently running refresh operation, if one is in progress.
20+
* Otherwise returns a promise that resolves immediately.
21+
*/
22+
public waitForCurrentRefresh(): Promise<void> {
23+
return this.currentDiscoveryPromise ?? Promise.resolve();
24+
}
25+
1826
/**
1927
* Force the discovery process to run. Normally invoked by the derived class when a relevant file
2028
* system change is detected.
29+
*
30+
* Returns a promise that resolves when the refresh is complete, including any retries.
2131
*/
22-
public refresh(): void {
32+
public refresh(): Promise<void> {
2333
// We avoid having multiple discovery operations in progress at the same time. Otherwise, if we
2434
// got a storm of refresh requests due to, say, the copying or deletion of a large directory
2535
// tree, we could potentially spawn a separate simultaneous discovery operation for each
@@ -36,49 +46,48 @@ export abstract class Discovery<T> extends DisposableObject {
3646
// other change notifications that might be coming along. However, this would create more
3747
// latency in the common case, in order to save a bit of latency in the uncommon case.
3848

39-
if (this.discoveryInProgress) {
49+
if (this.currentDiscoveryPromise !== undefined) {
4050
// There's already a discovery operation in progress. Tell it to restart when it's done.
41-
this.retry = true;
51+
this.restartWhenFinished = true;
4252
} else {
4353
// No discovery in progress, so start one now.
44-
this.discoveryInProgress = true;
45-
this.launchDiscovery();
54+
this.currentDiscoveryPromise = this.launchDiscovery().finally(() => {
55+
this.currentDiscoveryPromise = undefined;
56+
});
4657
}
58+
return this.currentDiscoveryPromise;
4759
}
4860

4961
/**
5062
* Starts the asynchronous discovery operation by invoking the `discover` function. When the
5163
* discovery operation completes, the `update` function will be invoked with the results of the
5264
* discovery.
5365
*/
54-
private launchDiscovery(): void {
55-
const discoveryPromise = this.discover();
56-
discoveryPromise
57-
.then((results) => {
58-
if (!this.retry) {
59-
// Update any listeners with the results of the discovery.
60-
this.discoveryInProgress = false;
61-
this.update(results);
62-
}
63-
})
64-
65-
.catch((err: unknown) => {
66-
void extLogger.log(
67-
`${this.name} failed. Reason: ${getErrorMessage(err)}`,
68-
);
69-
})
66+
private async launchDiscovery(): Promise<void> {
67+
let results: T | undefined;
68+
try {
69+
results = await this.discover();
70+
} catch (err) {
71+
void extLogger.log(
72+
`${this.name} failed. Reason: ${getErrorMessage(err)}`,
73+
);
74+
results = undefined;
75+
}
7076

71-
.finally(() => {
72-
if (this.retry) {
73-
// Another refresh request came in while we were still running a previous discovery
74-
// operation. Since the discovery results we just computed are now stale, we'll launch
75-
// another discovery operation instead of updating.
76-
// Note that by doing this inside of `finally`, we will relaunch discovery even if the
77-
// initial discovery operation failed.
78-
this.retry = false;
79-
this.launchDiscovery();
80-
}
81-
});
77+
if (this.restartWhenFinished) {
78+
// Another refresh request came in while we were still running a previous discovery
79+
// operation. Since the discovery results we just computed are now stale, we'll launch
80+
// another discovery operation instead of updating.
81+
// We want to relaunch discovery regardless of if the initial discovery operation
82+
// succeeded or failed.
83+
this.restartWhenFinished = false;
84+
await this.launchDiscovery();
85+
} else {
86+
// If the discovery was successful, then update any listeners with the results.
87+
if (results !== undefined) {
88+
this.update(results);
89+
}
90+
}
8291
}
8392

8493
/**

extensions/ql-vscode/src/queries-panel/queries-module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class QueriesModule extends DisposableObject {
2121

2222
const queryDiscovery = new QueryDiscovery(app, cliServer);
2323
this.push(queryDiscovery);
24-
queryDiscovery.refresh();
24+
void queryDiscovery.refresh();
2525

2626
const queriesPanel = new QueriesPanel(queryDiscovery);
2727
this.push(queriesPanel);

extensions/ql-vscode/src/query-testing/qltest-discovery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
6464

6565
private handleDidChange(uri: Uri): void {
6666
if (!QLTestDiscovery.ignoreTestPath(uri.fsPath)) {
67-
this.refresh();
67+
void this.refresh();
6868
}
6969
}
7070
protected async discover(): Promise<QLTestDiscoveryResults> {

extensions/ql-vscode/src/query-testing/test-adapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
115115
this.qlTestDiscovery = this.push(
116116
new QLTestDiscovery(workspaceFolder, cliServer),
117117
);
118-
this.qlTestDiscovery.refresh();
118+
void this.qlTestDiscovery.refresh();
119119

120120
this.push(this.qlTestDiscovery.onDidChangeTests(this.discoverTests, this));
121121
}

extensions/ql-vscode/src/query-testing/test-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class WorkspaceFolderHandler extends DisposableObject {
9292
this.push(
9393
this.testDiscovery.onDidChangeTests(this.handleDidChangeTests, this),
9494
);
95-
this.testDiscovery.refresh();
95+
void this.testDiscovery.refresh();
9696
}
9797

9898
private handleDidChangeTests(): void {

extensions/ql-vscode/test/vscode-tests/minimal-workspace/queries-panel/query-discovery.test.ts

Lines changed: 39 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,10 @@ import {
66
workspace,
77
} from "vscode";
88
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
9-
import {
10-
QueryDiscovery,
11-
QueryDiscoveryResults,
12-
} from "../../../../src/queries-panel/query-discovery";
9+
import { QueryDiscovery } from "../../../../src/queries-panel/query-discovery";
1310
import { createMockApp } from "../../../__mocks__/appMock";
1411
import { mockedObject } from "../../utils/mocking.helpers";
1512
import { basename, join, sep } from "path";
16-
import { sleep } from "../../../../src/pure/time";
1713

1814
describe("QueryDiscovery", () => {
1915
beforeEach(() => {
@@ -28,11 +24,10 @@ describe("QueryDiscovery", () => {
2824
});
2925

3026
const discovery = new QueryDiscovery(createMockApp({}), cli);
31-
const results: QueryDiscoveryResults = await (
32-
discovery as any
33-
).discover();
27+
await discovery.refresh();
28+
const queries = discovery.queries;
3429

35-
expect(results.queries).toEqual([]);
30+
expect(queries).toEqual([]);
3631
expect(resolveQueries).toHaveBeenCalledTimes(1);
3732
});
3833

@@ -49,22 +44,18 @@ describe("QueryDiscovery", () => {
4944
});
5045

5146
const discovery = new QueryDiscovery(createMockApp({}), cli);
52-
const results: QueryDiscoveryResults = await (
53-
discovery as any
54-
).discover();
55-
56-
expect(results.queries[0].children.length).toEqual(3);
57-
expect(results.queries[0].children[0].name).toEqual("dir1");
58-
expect(results.queries[0].children[0].children.length).toEqual(1);
59-
expect(results.queries[0].children[0].children[0].name).toEqual(
60-
"query1.ql",
61-
);
62-
expect(results.queries[0].children[1].name).toEqual("dir2");
63-
expect(results.queries[0].children[1].children.length).toEqual(1);
64-
expect(results.queries[0].children[1].children[0].name).toEqual(
65-
"query2.ql",
66-
);
67-
expect(results.queries[0].children[2].name).toEqual("query3.ql");
47+
await discovery.refresh();
48+
const queries = discovery.queries;
49+
expect(queries).toBeDefined();
50+
51+
expect(queries![0].children.length).toEqual(3);
52+
expect(queries![0].children[0].name).toEqual("dir1");
53+
expect(queries![0].children[0].children.length).toEqual(1);
54+
expect(queries![0].children[0].children[0].name).toEqual("query1.ql");
55+
expect(queries![0].children[1].name).toEqual("dir2");
56+
expect(queries![0].children[1].children.length).toEqual(1);
57+
expect(queries![0].children[1].children[0].name).toEqual("query2.ql");
58+
expect(queries![0].children[2].name).toEqual("query3.ql");
6859
});
6960

7061
it("should collapse directories containing only a single element", async () => {
@@ -79,25 +70,21 @@ describe("QueryDiscovery", () => {
7970
});
8071

8172
const discovery = new QueryDiscovery(createMockApp({}), cli);
82-
const results: QueryDiscoveryResults = await (
83-
discovery as any
84-
).discover();
85-
86-
expect(results.queries[0].children.length).toEqual(1);
87-
expect(results.queries[0].children[0].name).toEqual("dir1");
88-
expect(results.queries[0].children[0].children.length).toEqual(2);
89-
expect(results.queries[0].children[0].children[0].name).toEqual(
73+
await discovery.refresh();
74+
const queries = discovery.queries;
75+
expect(queries).toBeDefined();
76+
77+
expect(queries![0].children.length).toEqual(1);
78+
expect(queries![0].children[0].name).toEqual("dir1");
79+
expect(queries![0].children[0].children.length).toEqual(2);
80+
expect(queries![0].children[0].children[0].name).toEqual(
9081
"dir2 / dir3 / dir3",
9182
);
92-
expect(
93-
results.queries[0].children[0].children[0].children.length,
94-
).toEqual(1);
95-
expect(
96-
results.queries[0].children[0].children[0].children[0].name,
97-
).toEqual("query2.ql");
98-
expect(results.queries[0].children[0].children[1].name).toEqual(
99-
"query1.ql",
83+
expect(queries![0].children[0].children[0].children.length).toEqual(1);
84+
expect(queries![0].children[0].children[0].children[0].name).toEqual(
85+
"query2.ql",
10086
);
87+
expect(queries![0].children[0].children[1].name).toEqual("query1.ql");
10188
});
10289

10390
it("calls resolveQueries once for each workspace folder", async () => {
@@ -128,14 +115,14 @@ describe("QueryDiscovery", () => {
128115
});
129116

130117
const discovery = new QueryDiscovery(createMockApp({}), cli);
131-
const results: QueryDiscoveryResults = await (
132-
discovery as any
133-
).discover();
118+
await discovery.refresh();
119+
const queries = discovery.queries;
120+
expect(queries).toBeDefined();
134121

135-
expect(results.queries.length).toEqual(3);
136-
expect(results.queries[0].children[0].name).toEqual("query1.ql");
137-
expect(results.queries[1].children[0].name).toEqual("query2.ql");
138-
expect(results.queries[2].children[0].name).toEqual("query3.ql");
122+
expect(queries!.length).toEqual(3);
123+
expect(queries![0].children[0].name).toEqual("query1.ql");
124+
expect(queries![1].children[0].name).toEqual("query2.ql");
125+
expect(queries![2].children[0].name).toEqual("query3.ql");
139126

140127
expect(resolveQueries).toHaveBeenCalledTimes(3);
141128
});
@@ -176,16 +163,14 @@ describe("QueryDiscovery", () => {
176163
const onDidChangeQueriesSpy = jest.fn();
177164
discovery.onDidChangeQueries(onDidChangeQueriesSpy);
178165

179-
const results = await (discovery as any).discover();
180-
(discovery as any).update(results);
166+
await discovery.refresh();
181167

182168
expect(createFileSystemWatcherSpy).toHaveBeenCalledTimes(2);
183169
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(1);
184170

185171
onWatcherDidChangeEvent.fire(workspace.workspaceFolders![0].uri);
186172

187-
// Wait for refresh to finish
188-
await sleep(100);
173+
await discovery.waitForCurrentRefresh();
189174

190175
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(2);
191176
});
@@ -209,15 +194,13 @@ describe("QueryDiscovery", () => {
209194
const onDidChangeQueriesSpy = jest.fn();
210195
discovery.onDidChangeQueries(onDidChangeQueriesSpy);
211196

212-
const results = await (discovery as any).discover();
213-
(discovery as any).update(results);
197+
await discovery.refresh();
214198

215199
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(1);
216200

217201
onDidChangeWorkspaceFoldersEvent.fire({ added: [], removed: [] });
218202

219-
// Wait for refresh to finish
220-
await sleep(100);
203+
await discovery.waitForCurrentRefresh();
221204

222205
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(2);
223206
});

extensions/ql-vscode/test/vscode-tests/minimal-workspace/query-testing/qltest-discovery.test.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@ describe("qltest-discovery", () => {
5252
});
5353

5454
it("should run discovery", async () => {
55-
const result = await (qlTestDiscover as any).discover();
56-
expect(result.watchPath).toEqualPath(baseDir);
57-
expect(result.testDirectory.path).toEqualPath(baseDir);
58-
expect(result.testDirectory.name).toBe("My tests");
55+
await qlTestDiscover.refresh();
56+
const testDirectory = qlTestDiscover.testDirectory;
57+
expect(testDirectory).toBeDefined();
5958

60-
let children = result.testDirectory.children;
59+
expect(testDirectory!.path).toEqualPath(baseDir);
60+
expect(testDirectory!.name).toBe("My tests");
61+
62+
let children = testDirectory!.children;
6163
expect(children.length).toBe(1);
6264

6365
expect(children[0].path).toEqualPath(cDir);
@@ -83,12 +85,14 @@ describe("qltest-discovery", () => {
8385
it("should avoid discovery if a folder does not exist", async () => {
8486
await fs.remove(baseDir);
8587

86-
const result = await (qlTestDiscover as any).discover();
87-
expect(result.watchPath).toEqualPath(baseDir);
88-
expect(result.testDirectory.path).toEqualPath(baseDir);
89-
expect(result.testDirectory.name).toBe("My tests");
88+
await qlTestDiscover.refresh();
89+
const testDirectory = qlTestDiscover.testDirectory;
90+
expect(testDirectory).toBeDefined();
91+
92+
expect(testDirectory!.path).toEqualPath(baseDir);
93+
expect(testDirectory!.name).toBe("My tests");
9094

91-
expect(result.testDirectory.children.length).toBe(0);
95+
expect(testDirectory!.children.length).toBe(0);
9296
});
9397
});
9498
});

0 commit comments

Comments
 (0)