Skip to content

Commit 7a35f0d

Browse files
committed
Address third WSL Bugbot review pass
- Drain in-flight startBackend before stopBackendAndWaitForExit so a WSL config update no longer races past a restart that is awaiting wslpath and throws a misleading error while the in-flight call spawns with stale config. - Run wslpath inside the distro the user actually picked when they select a path under \wsl.localhost\<distro>\... rather than always using wslConfig.distro, so multi-distro setups convert correctly. - Revert DEFAULT_TIMEOUT_MS in backendReadiness back to 30s; the only caller passes 60s explicitly, so the 5-minute default served no purpose and risked silently slowing future callers. - Cover extractDistroFromUncPath in wsl.test.ts.
1 parent 07b72a7 commit 7a35f0d

4 files changed

Lines changed: 54 additions & 5 deletions

File tree

apps/desktop/src/backendReadiness.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface WaitForHttpReadyOptions {
88
readonly isReady?: (response: Response) => boolean;
99
}
1010

11-
const DEFAULT_TIMEOUT_MS = 300_000;
11+
const DEFAULT_TIMEOUT_MS = 30_000;
1212
const DEFAULT_INTERVAL_MS = 100;
1313
const DEFAULT_REQUEST_TIMEOUT_MS = 1_000;
1414

apps/desktop/src/main.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { showDesktopConfirmDialog } from "./confirmDialog.ts";
5858
import { resolveDesktopServerExposure } from "./serverExposure.ts";
5959
import {
6060
DISTRO_NAME_PATTERN,
61+
extractDistroFromUncPath,
6162
isWslAvailable,
6263
listWslDistrosAsync,
6364
loadWslConfig,
@@ -215,7 +216,7 @@ type LinuxDesktopNamedApp = Electron.App & {
215216

216217
let mainWindow: BrowserWindow | null = null;
217218
let backendProcess: ChildProcess.ChildProcess | null = null;
218-
let backendStartInFlight = false;
219+
let backendStartInFlight: Promise<void> | null = null;
219220
let backendPort = 0;
220221
let backendBindHost = DESKTOP_LOOPBACK_HOST;
221222
let backendBootstrapToken = "";
@@ -1416,12 +1417,16 @@ async function startBackend(): Promise<boolean> {
14161417
scheduleBackendRestart(`missing server entry at ${windowsEntry}`);
14171418
return false;
14181419
}
1419-
backendStartInFlight = true;
1420+
let resolveStartInFlight!: () => void;
1421+
backendStartInFlight = new Promise<void>((resolve) => {
1422+
resolveStartInFlight = resolve;
1423+
});
14201424
let linuxEntry: string | null;
14211425
try {
14221426
linuxEntry = await windowsToWslPathAsync(wslConfig.distro, windowsEntry);
14231427
} finally {
1424-
backendStartInFlight = false;
1428+
backendStartInFlight = null;
1429+
resolveStartInFlight();
14251430
}
14261431
if (isQuitting || backendProcess) return false;
14271432
if (linuxEntry === null) {
@@ -1576,6 +1581,15 @@ function stopBackend(): void {
15761581
}
15771582

15781583
async function stopBackendAndWaitForExit(timeoutMs = 5_000): Promise<void> {
1584+
// Drain any in-flight startBackend (e.g. an exponential-backoff restart still
1585+
// awaiting wslpath) so backendProcess reflects either a fully spawned child
1586+
// we should kill or a failed start. Otherwise a config update can race past
1587+
// the in-flight call and throw a misleading error while a backend still
1588+
// launches with stale config.
1589+
const inFlight = backendStartInFlight;
1590+
if (inFlight) {
1591+
await inFlight;
1592+
}
15791593
cancelBackendReadinessWait();
15801594
backendListeningDetector = null;
15811595
if (restartTimer) {
@@ -1767,7 +1781,11 @@ function registerIpcHandlers(): void {
17671781
const pickedPath = result.filePaths[0] ?? null;
17681782
if (pickedPath === null) return null;
17691783
if (!useWsl) return pickedPath;
1770-
const wslPath = await windowsToWslPathAsync(wslConfig.distro, pickedPath);
1784+
// If the user picked under \\wsl.localhost\<distro>\..., run wslpath inside
1785+
// that distro instead of the configured one — otherwise the conversion
1786+
// either fails or yields a path that points at the wrong filesystem.
1787+
const targetDistro = extractDistroFromUncPath(pickedPath) ?? wslConfig.distro;
1788+
const wslPath = await windowsToWslPathAsync(targetDistro, pickedPath);
17711789
if (wslPath === null) {
17721790
throw new Error("Could not convert the selected folder path for WSL.");
17731791
}

apps/desktop/src/wsl.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as OS from "node:os";
66
import { Readable } from "node:stream";
77
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
88
import {
9+
extractDistroFromUncPath,
910
parseWslDistroList,
1011
loadWslConfig,
1112
resolveWslHomeUncPath,
@@ -114,6 +115,26 @@ describe("windowsToWslPathAsync", () => {
114115
});
115116
});
116117

118+
describe("extractDistroFromUncPath", () => {
119+
it("extracts the distro from \\\\wsl.localhost UNC paths", () => {
120+
expect(extractDistroFromUncPath("\\\\wsl.localhost\\Ubuntu-22.04\\home\\josh")).toBe(
121+
"Ubuntu-22.04",
122+
);
123+
});
124+
125+
it("extracts the distro from the legacy \\\\wsl$ UNC paths", () => {
126+
expect(extractDistroFromUncPath("\\\\wsl$\\Debian\\home\\josh")).toBe("Debian");
127+
});
128+
129+
it("returns null for non-UNC Windows paths", () => {
130+
expect(extractDistroFromUncPath("C:\\Users\\Josh\\project")).toBeNull();
131+
});
132+
133+
it("returns null when the segment is not a valid distro name", () => {
134+
expect(extractDistroFromUncPath("\\\\wsl.localhost\\bad name!\\home")).toBeNull();
135+
});
136+
});
137+
117138
describe("resolveWslHomeUncPath", () => {
118139
const distros = [
119140
{ name: "Debian", isDefault: true, version: 2 as const },

apps/desktop/src/wsl.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ export function parseWslDistroList(stdout: Buffer): WslDistro[] {
5151
return distros;
5252
}
5353

54+
// Recognizes \\wsl.localhost\<distro>\... and the legacy \\wsl$\<distro>\... so
55+
// `wslpath` can be invoked inside the distro that actually owns the path,
56+
// rather than whichever distro is configured for the desktop backend.
57+
export function extractDistroFromUncPath(windowsPath: string): string | null {
58+
const match = /^\\\\(?:wsl\.localhost|wsl\$)\\([^\\]+)/i.exec(windowsPath);
59+
if (!match) return null;
60+
const candidate = match[1]!;
61+
return DISTRO_NAME_PATTERN.test(candidate) ? candidate : null;
62+
}
63+
5464
export function windowsToWslPathAsync(
5565
distro: string | null,
5666
windowsPath: string,

0 commit comments

Comments
 (0)