Skip to content

Commit 13e1b6c

Browse files
committed
Use file for storing distribution state instead of globalState
1 parent 8a27b45 commit 13e1b6c

File tree

3 files changed

+111
-28
lines changed

3 files changed

+111
-28
lines changed

extensions/ql-vscode/src/codeql-cli/distribution.ts

Lines changed: 105 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
createWriteStream,
44
mkdtemp,
55
pathExists,
6+
readJson,
67
remove,
78
writeJson,
89
} from "fs-extra";
@@ -25,7 +26,9 @@ import {
2526
InvocationRateLimiter,
2627
InvocationRateLimiterResultKind,
2728
} from "../common/invocation-rate-limiter";
29+
import type { NotificationLogger } from "../common/logging";
2830
import {
31+
showAndLogExceptionWithTelemetry,
2932
showAndLogErrorMessage,
3033
showAndLogWarningMessage,
3134
} from "../common/logging";
@@ -35,6 +38,10 @@ import type { Release } from "./distribution/release";
3538
import { ReleasesApiConsumer } from "./distribution/releases-api-consumer";
3639
import { createTimeoutSignal } from "../common/fetch-stream";
3740
import { withDistributionUpdateLock } from "./lock";
41+
import { asError, getErrorMessage } from "../common/helpers-pure";
42+
import { isIOError } from "../common/files";
43+
import { telemetryListener } from "../common/vscode/telemetry";
44+
import { redactableError } from "../common/errors";
3845

3946
/**
4047
* distribution.ts
@@ -60,6 +67,11 @@ const NIGHTLY_DISTRIBUTION_REPOSITORY_NWO = "dsp-testing/codeql-cli-nightlies";
6067
*/
6168
export const DEFAULT_DISTRIBUTION_VERSION_RANGE: Range = new Range("2.x");
6269

70+
interface DistributionState {
71+
folderIndex: number;
72+
release: Release | null;
73+
}
74+
6375
export interface DistributionProvider {
6476
getCodeQlPathWithoutVersionCheck(): Promise<string | undefined>;
6577
onDidChangeDistribution?: Event<void>;
@@ -71,13 +83,15 @@ export class DistributionManager implements DistributionProvider {
7183
public readonly config: DistributionConfig,
7284
private readonly versionRange: Range,
7385
extensionContext: ExtensionContext,
86+
logger: NotificationLogger,
7487
) {
7588
this._onDidChangeDistribution = config.onDidChangeConfiguration;
7689
this.extensionSpecificDistributionManager =
7790
new ExtensionSpecificDistributionManager(
7891
config,
7992
versionRange,
8093
extensionContext,
94+
logger,
8195
);
8296
this.updateCheckRateLimiter = new InvocationRateLimiter(
8397
extensionContext.globalState,
@@ -87,6 +101,10 @@ export class DistributionManager implements DistributionProvider {
87101
);
88102
}
89103

104+
public async initialize(): Promise<void> {
105+
await this.extensionSpecificDistributionManager.initialize();
106+
}
107+
90108
/**
91109
* Look up a CodeQL launcher binary.
92110
*/
@@ -287,14 +305,53 @@ export class DistributionManager implements DistributionProvider {
287305
}
288306

289307
class ExtensionSpecificDistributionManager {
308+
private distributionState: DistributionState | undefined;
309+
290310
constructor(
291311
private readonly config: DistributionConfig,
292312
private readonly versionRange: Range,
293313
private readonly extensionContext: ExtensionContext,
314+
private readonly logger: NotificationLogger,
294315
) {
295316
/**/
296317
}
297318

319+
public async initialize() {
320+
const distributionStatePath = this.getDistributionStatePath();
321+
try {
322+
this.distributionState = await readJson(distributionStatePath);
323+
} catch (e: unknown) {
324+
if (isIOError(e) && e.code === "ENOENT") {
325+
// If the file doesn't exist, that just means we need to create it
326+
327+
this.distributionState = {
328+
folderIndex: this.extensionContext.globalState.get(
329+
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey,
330+
0,
331+
),
332+
release: (this.extensionContext.globalState.get(
333+
ExtensionSpecificDistributionManager._installedReleaseStateKey,
334+
) ?? null) as Release | null,
335+
};
336+
337+
// This may result in a race condition, but when this happens both processes should write the same file.
338+
await writeJson(distributionStatePath, this.distributionState);
339+
} else {
340+
void showAndLogExceptionWithTelemetry(
341+
this.logger,
342+
telemetryListener,
343+
redactableError(
344+
asError(e),
345+
)`Failed to read distribution state from ${distributionStatePath}: ${getErrorMessage(e)}`,
346+
);
347+
this.distributionState = {
348+
folderIndex: 0,
349+
release: null,
350+
};
351+
}
352+
}
353+
}
354+
298355
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
299356
if (this.getInstalledRelease() !== undefined) {
300357
// An extension specific distribution has been installed.
@@ -357,14 +414,7 @@ class ExtensionSpecificDistributionManager {
357414
release: Release,
358415
progressCallback?: ProgressCallback,
359416
): Promise<void> {
360-
const distributionStatePath = join(
361-
this.extensionContext.globalStorageUri.fsPath,
362-
ExtensionSpecificDistributionManager._distributionStateFilename,
363-
);
364-
if (!(await pathExists(distributionStatePath))) {
365-
// This may result in a race condition, but when this happens both processes should write the same file.
366-
await writeJson(distributionStatePath, {});
367-
}
417+
const distributionStatePath = this.getDistributionStatePath();
368418

369419
await withDistributionUpdateLock(
370420
// .lock will be appended to this filename
@@ -586,23 +636,19 @@ class ExtensionSpecificDistributionManager {
586636
}
587637

588638
private async bumpDistributionFolderIndex(): Promise<void> {
589-
const index = this.extensionContext.globalState.get(
590-
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey,
591-
0,
592-
);
593-
await this.extensionContext.globalState.update(
594-
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey,
595-
index + 1,
596-
);
639+
await this.updateState((oldState) => {
640+
return {
641+
...oldState,
642+
folderIndex: oldState.folderIndex + 1,
643+
};
644+
});
597645
}
598646

599647
private getDistributionStoragePath(): string {
648+
const distributionState = this.getDistributionState();
649+
600650
// Use an empty string for the initial distribution for backwards compatibility.
601-
const distributionFolderIndex =
602-
this.extensionContext.globalState.get(
603-
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey,
604-
0,
605-
) || "";
651+
const distributionFolderIndex = distributionState.folderIndex || "";
606652
return join(
607653
this.extensionContext.globalStorageUri.fsPath,
608654
ExtensionSpecificDistributionManager._currentDistributionFolderBaseName +
@@ -617,19 +663,50 @@ class ExtensionSpecificDistributionManager {
617663
);
618664
}
619665

620-
private getInstalledRelease(): Release | undefined {
621-
return this.extensionContext.globalState.get(
622-
ExtensionSpecificDistributionManager._installedReleaseStateKey,
666+
private getDistributionStatePath(): string {
667+
return join(
668+
this.extensionContext.globalStorageUri.fsPath,
669+
ExtensionSpecificDistributionManager._distributionStateFilename,
623670
);
624671
}
625672

673+
private getInstalledRelease(): Release | undefined {
674+
return this.getDistributionState().release ?? undefined;
675+
}
676+
626677
private async storeInstalledRelease(
627678
release: Release | undefined,
628679
): Promise<void> {
629-
await this.extensionContext.globalState.update(
630-
ExtensionSpecificDistributionManager._installedReleaseStateKey,
631-
release,
632-
);
680+
await this.updateState((oldState) => ({
681+
...oldState,
682+
release: release ?? null,
683+
}));
684+
}
685+
686+
private getDistributionState(): DistributionState {
687+
const distributionState = this.distributionState;
688+
if (distributionState === undefined) {
689+
throw new Error(
690+
"Invariant violation: distribution state not initialized",
691+
);
692+
}
693+
return distributionState;
694+
}
695+
696+
private async updateState(
697+
f: (oldState: DistributionState) => DistributionState,
698+
) {
699+
const oldState = this.distributionState;
700+
if (oldState === undefined) {
701+
throw new Error(
702+
"Invariant violation: distribution state not initialized",
703+
);
704+
}
705+
const newState = f(oldState);
706+
this.distributionState = newState;
707+
708+
const distributionStatePath = this.getDistributionStatePath();
709+
await writeJson(distributionStatePath, newState);
633710
}
634711

635712
private static readonly _currentDistributionFolderBaseName = "distribution";

extensions/ql-vscode/src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ export async function activate(
362362
distributionConfigListener,
363363
codeQlVersionRange,
364364
ctx,
365+
app.logger,
365366
);
367+
await distributionManager.initialize();
366368

367369
registerErrorStubs([checkForUpdatesCommand], (command) => async () => {
368370
void showAndLogErrorMessage(

extensions/ql-vscode/test/vscode-tests/no-workspace/codeql-cli/distribution.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
showAndLogErrorMessage,
1414
showAndLogWarningMessage,
1515
} from "../../../../src/common/logging";
16+
import { createMockLogger } from "../../../__mocks__/loggerMock";
1617

1718
jest.mock("os", () => {
1819
const original = jest.requireActual("os");
@@ -108,6 +109,7 @@ describe("Launcher path", () => {
108109
{ customCodeQlPath: pathToCmd } as any,
109110
{} as any,
110111
{} as any,
112+
createMockLogger(),
111113
);
112114

113115
const result = await manager.getCodeQlPathWithoutVersionCheck();
@@ -126,6 +128,7 @@ describe("Launcher path", () => {
126128
{ customCodeQlPath: pathToCmd } as any,
127129
{} as any,
128130
{} as any,
131+
createMockLogger(),
129132
);
130133

131134
const result = await manager.getCodeQlPathWithoutVersionCheck();
@@ -141,6 +144,7 @@ describe("Launcher path", () => {
141144
{ customCodeQlPath: pathToCmd } as any,
142145
{} as any,
143146
{} as any,
147+
createMockLogger(),
144148
);
145149

146150
const result = await manager.getCodeQlPathWithoutVersionCheck();

0 commit comments

Comments
 (0)