Skip to content

Commit 4631e82

Browse files
committed
More error handling
1 parent 73a9ef2 commit 4631e82

File tree

3 files changed

+53
-4
lines changed

3 files changed

+53
-4
lines changed

src/core/cliManager.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,57 @@ export class CliManager {
165165
"Moving existing binary to",
166166
path.basename(oldBinPath),
167167
);
168-
await fs.rename(binPath, oldBinPath);
168+
try {
169+
await fs.rename(binPath, oldBinPath);
170+
} catch (error) {
171+
// That's fine since we are trying to move the file anyway
172+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
173+
throw error;
174+
}
175+
this.output.debug(
176+
"Binary already moved by another process, continuing",
177+
);
178+
}
169179
}
170180

171181
// Then move the temporary binary into the right place.
172182
this.output.info("Moving downloaded file to", path.basename(binPath));
173183
await fs.mkdir(path.dirname(binPath), { recursive: true });
174-
await fs.rename(tempFile, binPath);
184+
try {
185+
await fs.rename(tempFile, binPath);
186+
} catch (error) {
187+
const errCode = (error as NodeJS.ErrnoException).code;
188+
// On Windows, fs.rename fails if the target exists. On POSIX systems,
189+
// it atomically replaces the target. If another process already wrote
190+
// the binary and it's the correct version, we can consider this a
191+
// success since both processes are installing the same version.
192+
if (
193+
errCode === "EPERM" ||
194+
errCode === "EACCES" ||
195+
errCode === "EBUSY"
196+
) {
197+
this.output.debug(
198+
"Binary may be in use or already exists, verifying version",
199+
);
200+
const existingStat = await cliUtils.stat(binPath);
201+
if (existingStat !== undefined) {
202+
try {
203+
const existingVersion = await cliUtils.version(binPath);
204+
const expectedVersion = buildInfo.version;
205+
if (existingVersion === expectedVersion) {
206+
this.output.info(
207+
"Binary already installed by another process with correct version, cleaning up temp file",
208+
);
209+
await fs.rm(tempFile, { force: true });
210+
return binPath;
211+
}
212+
} catch {
213+
// If we can't verify the version, fall through to throw original error
214+
}
215+
}
216+
}
217+
throw error;
218+
}
175219

176220
// For debugging, to see if the binary only partially downloaded.
177221
const newStat = await cliUtils.stat(binPath);

src/core/cliUtils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,12 @@ export async function rmOld(binPath: string): Promise<RemovalResult[]> {
104104
await fs.rm(filePath, { force: true });
105105
results.push({ fileName, error: undefined });
106106
} catch (error) {
107-
results.push({ fileName, error });
107+
// That's fine since we were trying to delete this file anyway
108+
if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
109+
results.push({ fileName, error: undefined });
110+
} else {
111+
results.push({ fileName, error });
112+
}
108113
}
109114
}
110115
}

test/unit/core/cliManager.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ describe("CliManager", () => {
353353
writeOldFile(path.join(BINARY_DIR, "coder.old-xyz"), "old");
354354
writeOldFile(path.join(BINARY_DIR, "coder.temp-abc"), "temp");
355355
writeOldFile(path.join(BINARY_DIR, "keeper.txt"), "keep");
356-
// New files won't be deleted
356+
// New files won't be deleted (could be another download in-progress)
357357
memfs.writeFileSync(path.join(BINARY_DIR, "coder.asc"), "signature");
358358

359359
withSuccessfulDownload();

0 commit comments

Comments
 (0)