Skip to content

Commit 6d67d87

Browse files
committed
Address review comments
1 parent c339c59 commit 6d67d87

File tree

5 files changed

+48
-43
lines changed

5 files changed

+48
-43
lines changed

src/core/binaryLock.ts

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ import { type Logger } from "../logging/logger";
66

77
import * as downloadProgress from "./downloadProgress";
88

9+
/**
10+
* Timeout to detect stale lock files and take over from stuck processes.
11+
* This value is intentionally small so we can quickly takeover.
12+
*/
13+
const STALE_TIMEOUT_MS = 15000;
14+
15+
const LOCK_POLL_INTERVAL_MS = 500;
16+
17+
type LockRelease = () => Promise<void>;
18+
919
/**
1020
* Manages file locking for binary downloads to coordinate between multiple
1121
* VS Code windows downloading the same binary.
@@ -23,7 +33,7 @@ export class BinaryLock {
2333
async acquireLockOrWait(
2434
binPath: string,
2535
progressLogPath: string,
26-
): Promise<{ release: () => Promise<void>; waited: boolean }> {
36+
): Promise<{ release: LockRelease; waited: boolean }> {
2737
const release = await this.safeAcquireLock(binPath);
2838
if (release) {
2939
return { release, waited: false };
@@ -43,12 +53,10 @@ export class BinaryLock {
4353
* Attempt to acquire a lock on the binary file.
4454
* Returns the release function if successful, null if lock is already held.
4555
*/
46-
private async safeAcquireLock(
47-
path: string,
48-
): Promise<(() => Promise<void>) | null> {
56+
private async safeAcquireLock(path: string): Promise<LockRelease | null> {
4957
try {
5058
const release = await lockfile.lock(path, {
51-
stale: downloadProgress.STALE_TIMEOUT_MS,
59+
stale: STALE_TIMEOUT_MS,
5260
retries: 0,
5361
realpath: false,
5462
});
@@ -69,44 +77,50 @@ export class BinaryLock {
6977
private async monitorDownloadProgress(
7078
binPath: string,
7179
progressLogPath: string,
72-
): Promise<() => Promise<void>> {
80+
): Promise<LockRelease> {
7381
return await this.vscodeProposed.window.withProgress(
7482
{
7583
location: vscode.ProgressLocation.Notification,
7684
title: "Another window is downloading the Coder CLI binary",
7785
cancellable: false,
7886
},
7987
async (progress) => {
80-
return new Promise<() => Promise<void>>((resolve, reject) => {
81-
const interval = setInterval(async () => {
88+
return new Promise<LockRelease>((resolve, reject) => {
89+
const poll = async () => {
8290
try {
83-
const currentProgress =
84-
await downloadProgress.readProgress(progressLogPath);
85-
if (currentProgress) {
86-
const totalBytesPretty =
87-
currentProgress.totalBytes === null
88-
? "unknown"
89-
: prettyBytes(currentProgress.totalBytes);
90-
const message =
91-
currentProgress.status === "verifying"
92-
? "Verifying signature..."
93-
: `${prettyBytes(currentProgress.bytesDownloaded)} / ${totalBytesPretty}`;
94-
progress.report({ message });
95-
}
96-
91+
await this.updateProgressMonitor(progressLogPath, progress);
9792
const release = await this.safeAcquireLock(binPath);
9893
if (release) {
99-
clearInterval(interval);
100-
this.output.debug("Download completed by another process");
10194
return resolve(release);
10295
}
96+
// Schedule next poll only after current one completes
97+
setTimeout(poll, LOCK_POLL_INTERVAL_MS);
10398
} catch (error) {
104-
clearInterval(interval);
10599
reject(error);
106100
}
107-
}, 500);
101+
};
102+
poll().catch((error) => reject(error));
108103
});
109104
},
110105
);
111106
}
107+
108+
private async updateProgressMonitor(
109+
progressLogPath: string,
110+
progress: vscode.Progress<{ message?: string }>,
111+
): Promise<void> {
112+
const currentProgress =
113+
await downloadProgress.readProgress(progressLogPath);
114+
if (currentProgress) {
115+
const totalBytesPretty =
116+
currentProgress.totalBytes === null
117+
? "unknown"
118+
: prettyBytes(currentProgress.totalBytes);
119+
const message =
120+
currentProgress.status === "verifying"
121+
? "Verifying signature..."
122+
: `${prettyBytes(currentProgress.bytesDownloaded)} / ${totalBytesPretty}`;
123+
progress.report({ message });
124+
}
125+
}
112126
}

src/core/cliManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,8 @@ export class CliManager {
197197
reason: string,
198198
): Promise<boolean> {
199199
const choice = await this.vscodeProposed.window.showErrorMessage(
200-
`${reason}. Use version ${version} anyway?`,
200+
`${reason}. Run version ${version} anyway?`,
201201
"Run",
202-
"Exit",
203202
);
204203
return choice === "Run";
205204
}
@@ -273,6 +272,7 @@ export class CliManager {
273272
if (await this.promptUseExistingBinary(existingCheck.version, message)) {
274273
return binPath;
275274
}
275+
throw error;
276276
}
277277

278278
// Try .old-* binaries as fallback

src/core/cliUtils.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,15 @@ export async function findOldBinaries(binPath: string): Promise<string[]> {
122122
.map((f) => path.join(binDir, f));
123123

124124
// Sort by modification time, most recent first
125-
const stats = await Promise.all(
125+
const stats = await Promise.allSettled(
126126
oldBinaries.map(async (f) => ({
127127
path: f,
128128
mtime: (await fs.stat(f)).mtime,
129129
})),
130+
).then((result) =>
131+
result
132+
.filter((promise) => promise.status === "fulfilled")
133+
.map((promise) => promise.value),
130134
);
131135
stats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
132136
return stats.map((s) => s.path);
@@ -149,10 +153,6 @@ export function maybeWrapFileLockError(
149153
}
150154
return error;
151155
}
152-
export function isFileLockError(error: unknown): boolean {
153-
const code = (error as NodeJS.ErrnoException).code;
154-
return code === "EBUSY" || code === "EPERM";
155-
}
156156

157157
/**
158158
* Return the etag (sha1) of the path. Throw if unable to hash the file.

src/core/downloadProgress.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { promises as fs } from "node:fs";
22
import * as path from "node:path";
33

4-
export const STALE_TIMEOUT_MS = 15000;
5-
64
export interface DownloadProgress {
75
bytesDownloaded: number;
86
totalBytes: number | null;
@@ -38,11 +36,5 @@ export async function readProgress(
3836
}
3937

4038
export async function clearProgress(logPath: string): Promise<void> {
41-
try {
42-
await fs.rm(logPath, { force: true });
43-
} catch (error) {
44-
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
45-
throw error;
46-
}
47-
}
39+
await fs.rm(logPath, { force: true });
4840
}

test/unit/core/downloadProgress.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ describe("downloadProgress", () => {
6565
bytesDownloaded: 1500,
6666
totalBytes: 10000,
6767
status: "downloading",
68-
timestamp: Date.now(),
6968
};
7069

7170
await fs.writeFile(testLogPath, JSON.stringify(expectedProgress) + "\n");

0 commit comments

Comments
 (0)