Skip to content

Commit b2cc683

Browse files
committed
🤖 perf: shard OpenSSH masters and dedupe SSH project sync
- add an explicit OpenSSH master pool with sharded control sockets and lease-based exec reuse - hash remote project layouts and move workspace metadata/snapshot markers to project-scoped files - dedupe per-project bundle sync work and update unit plus SSH integration coverage for the new layout --- _Generated with `mux` • Model: `openai:gpt-5.4` • Thinking: `xhigh` • Cost: `$36.24`_ <!-- mux-attribution: model=openai:gpt-5.4 thinking=xhigh costs=36.24 -->
1 parent 3102607 commit b2cc683

15 files changed

+1313
-247
lines changed

src/node/runtime/DockerRuntime.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,10 @@ export class DockerRuntime extends RemoteRuntime {
353353
return `cd ${shescape.quote(cwd)}`;
354354
}
355355

356-
protected spawnRemoteProcess(fullCommand: string, _options: ExecOptions): Promise<SpawnResult> {
356+
protected spawnRemoteProcess(
357+
fullCommand: string,
358+
_options: ExecOptions & { deadlineMs?: number }
359+
): Promise<SpawnResult> {
357360
// Verify container name is available
358361
if (!this.containerName) {
359362
throw new RuntimeError(

src/node/runtime/RemoteRuntime.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ export interface SpawnResult {
4747
process: ChildProcess;
4848
/** Optional async work to do before exec (e.g., acquire connection) */
4949
preExec?: Promise<void>;
50+
/** Optional transport-scoped exit handling (e.g., master-pool health accounting). */
51+
onExit?: (exitCode: number, stderr: string) => void;
52+
/** Optional transport-scoped spawn error handling. */
53+
onError?: (error: Error) => void;
5054
}
5155

5256
/**
@@ -63,7 +67,7 @@ export abstract class RemoteRuntime implements Runtime {
6367
*/
6468
protected abstract spawnRemoteProcess(
6569
fullCommand: string,
66-
options: ExecOptions
70+
options: ExecOptions & { deadlineMs?: number }
6771
): Promise<SpawnResult>;
6872

6973
/**
@@ -138,8 +142,13 @@ export abstract class RemoteRuntime implements Runtime {
138142
}
139143

140144
// Spawn the remote process (SSH or Docker)
141-
// For SSH, this awaits connection pool backoff before spawning
142-
const { process: childProcess } = await this.spawnRemoteProcess(fullCommand, options);
145+
const timeoutMs = options.timeout !== undefined ? options.timeout * 1000 : undefined;
146+
const deadlineMs = timeoutMs !== undefined ? Date.now() + timeoutMs : undefined;
147+
const spawnResult = await this.spawnRemoteProcess(fullCommand, {
148+
...options,
149+
deadlineMs,
150+
});
151+
const { process: childProcess } = spawnResult;
143152

144153
// Short-lived commands can close stdin before writes/close complete.
145154
if (childProcess.stdin) {
@@ -163,23 +172,22 @@ export abstract class RemoteRuntime implements Runtime {
163172
// Create promises for exit code and duration immediately.
164173
const exitCode = new Promise<number>((resolve, reject) => {
165174
childProcess.on("close", (code, signal) => {
166-
if (aborted || options.abortSignal?.aborted) {
167-
resolve(EXIT_CODE_ABORTED);
168-
return;
169-
}
170-
if (timedOut) {
171-
resolve(EXIT_CODE_TIMEOUT);
172-
return;
173-
}
174-
const finalExitCode = code ?? (signal ? -1 : 0);
175-
175+
const finalExitCode =
176+
aborted || options.abortSignal?.aborted
177+
? EXIT_CODE_ABORTED
178+
: timedOut
179+
? EXIT_CODE_TIMEOUT
180+
: (code ?? (signal ? -1 : 0));
181+
182+
spawnResult.onExit?.(finalExitCode, stderrForErrorReporting);
176183
// Let subclass handle exit code (e.g., SSH connection pool)
177184
this.onExitCode(finalExitCode, options, stderrForErrorReporting);
178185

179186
resolve(finalExitCode);
180187
});
181188

182189
childProcess.on("error", (err) => {
190+
spawnResult.onError?.(err);
183191
reject(
184192
new RuntimeError(
185193
`Failed to execute ${this.commandPrefix} command: ${err.message}`,
@@ -226,8 +234,10 @@ export abstract class RemoteRuntime implements Runtime {
226234
void exitCode.finally(() => abortSignal.removeEventListener("abort", onAbort));
227235
}
228236

229-
// Handle timeout
230-
if (options.timeout !== undefined) {
237+
// Handle timeout. Include connection acquisition time in the local deadline so
238+
// user-configured timeouts do not silently stretch while the runtime waits for SSH capacity.
239+
if (timeoutMs !== undefined) {
240+
const remainingTimeoutMs = Math.max(0, (deadlineMs ?? Date.now()) - Date.now());
231241
const timeoutHandle = setTimeout(() => {
232242
timedOut = true;
233243

@@ -245,7 +255,7 @@ export abstract class RemoteRuntime implements Runtime {
245255
disposable[Symbol.dispose]();
246256
}, 1000);
247257
hardKillHandle.unref();
248-
}, options.timeout * 1000);
258+
}, remainingTimeoutMs);
249259

250260
void exitCode.finally(() => clearTimeout(timeoutHandle));
251261
}

src/node/runtime/SSHRuntime.test.ts

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, it, beforeEach, afterEach, spyOn } from "bun:test";
22
import * as runtimeHelpers from "@/node/utils/runtime/helpers";
33
import { SSHRuntime, computeBaseRepoPath } from "./SSHRuntime";
4+
import { buildRemoteProjectLayout, getRemoteWorkspacePath } from "./remoteProjectLayout";
45
import { createSSHTransport } from "./transports";
56

67
/**
@@ -149,10 +150,8 @@ describe("SSHRuntime base repo config normalization", () => {
149150
});
150151

151152
describe("SSHRuntime.createWorkspace", () => {
152-
it("uses directoryName for the workspace path while preparing the remote parent directory", async () => {
153-
const config = { host: "example.com", srcBaseDir: "/home/user/src" };
154-
const runtime = new SSHRuntime(config, createSSHTransport(config, false));
155-
const execSpy = spyOn(runtime, "exec").mockResolvedValue({
153+
function createExecStream(exitCode = 0) {
154+
return {
156155
stdout: new ReadableStream<Uint8Array>({
157156
start(controller) {
158157
controller.close();
@@ -164,18 +163,29 @@ describe("SSHRuntime.createWorkspace", () => {
164163
},
165164
}),
166165
stdin: new WritableStream<Uint8Array>(),
167-
exitCode: Promise.resolve(0),
166+
exitCode: Promise.resolve(exitCode),
168167
duration: Promise.resolve(0),
169-
});
170-
const readFileSpy = spyOn(runtime, "readFile").mockReturnValue(
171-
new ReadableStream<Uint8Array>({
172-
start(controller) {
173-
controller.error(new Error("missing branch map"));
174-
},
175-
})
168+
};
169+
}
170+
171+
it("uses directoryName for the workspace path while preparing the remote parent directory", async () => {
172+
const config = { host: "example.com", srcBaseDir: "/home/user/src" };
173+
const runtime = new SSHRuntime(config, createSSHTransport(config, false));
174+
const expectedLayout = buildRemoteProjectLayout(config.srcBaseDir, "/projects/demo");
175+
const expectedWorkspacePath = getRemoteWorkspacePath(expectedLayout, "review-slot");
176+
const execSpy = spyOn(runtime, "exec").mockImplementation(() =>
177+
Promise.resolve(createExecStream())
178+
);
179+
const readFileSpy = spyOn(runtime, "readFile").mockImplementation(
180+
() =>
181+
new ReadableStream<Uint8Array>({
182+
start(controller) {
183+
controller.error(new Error("missing branch metadata"));
184+
},
185+
})
176186
);
177-
const writeFileSpy = spyOn(runtime, "writeFile").mockReturnValue(
178-
new WritableStream<Uint8Array>()
187+
const writeFileSpy = spyOn(runtime, "writeFile").mockImplementation(
188+
() => new WritableStream<Uint8Array>()
179189
);
180190

181191
try {
@@ -194,13 +204,16 @@ describe("SSHRuntime.createWorkspace", () => {
194204

195205
expect(result).toEqual({
196206
success: true,
197-
workspacePath: "/home/user/src/demo/review-slot",
198-
});
199-
expect(execSpy).toHaveBeenCalledWith('mkdir -p "/home/user/src/demo"', {
200-
cwd: "/tmp",
201-
timeout: 10,
202-
abortSignal: undefined,
207+
workspacePath: expectedWorkspacePath,
203208
});
209+
expect(execSpy).toHaveBeenCalledWith(
210+
`mkdir -p ${JSON.stringify(expectedLayout.projectRoot)}`,
211+
{
212+
cwd: "/tmp",
213+
timeout: 10,
214+
abortSignal: undefined,
215+
}
216+
);
204217
} finally {
205218
execSpy.mockRestore();
206219
readFileSpy.mockRestore();
@@ -223,6 +236,8 @@ describe("SSHRuntime.deleteWorkspace", () => {
223236
it("deletes the mapped workspace branch instead of the current remote checkout", async () => {
224237
const config = { host: "example.com", srcBaseDir: "/home/user/src" };
225238
const runtime = new SSHRuntime(config, createSSHTransport(config, false));
239+
const expectedLayout = buildRemoteProjectLayout(config.srcBaseDir, "/projects/demo");
240+
const expectedDeletedPath = getRemoteWorkspacePath(expectedLayout, "review-slot");
226241
const execSpy = spyOn(runtime, "exec").mockImplementation((command) => {
227242
if (command.includes("git diff --quiet") || command.includes("test -d")) {
228243
return Promise.resolve(createExecStream(0));
@@ -232,13 +247,14 @@ describe("SSHRuntime.deleteWorkspace", () => {
232247
}
233248
throw new Error(`Unexpected exec command: ${command}`);
234249
});
235-
const readFileSpy = spyOn(runtime, "readFile").mockReturnValue(
236-
new ReadableStream<Uint8Array>({
237-
start(controller) {
238-
controller.enqueue(new TextEncoder().encode('{"review-slot":"feature-branch"}\n'));
239-
controller.close();
240-
},
241-
})
250+
const readFileSpy = spyOn(runtime, "readFile").mockImplementation(
251+
() =>
252+
new ReadableStream<Uint8Array>({
253+
start(controller) {
254+
controller.enqueue(new TextEncoder().encode('{"review-slot":"feature-branch"}\n'));
255+
controller.close();
256+
},
257+
})
242258
);
243259
const execBufferedSpy = spyOn(runtimeHelpers, "execBuffered").mockImplementation(
244260
(_runtime, command) => {
@@ -261,7 +277,7 @@ describe("SSHRuntime.deleteWorkspace", () => {
261277
const result = await runtime.deleteWorkspace("/projects/demo", "review-slot", true);
262278
expect(result).toEqual({
263279
success: true,
264-
deletedPath: "/home/user/src/demo/review-slot",
280+
deletedPath: expectedDeletedPath,
265281
});
266282
} finally {
267283
execSpy.mockRestore();
@@ -396,14 +412,16 @@ describe("SSHRuntime.resolvePath", () => {
396412
});
397413
describe("computeBaseRepoPath", () => {
398414
it("computes the correct bare repo path", () => {
399-
// computeBaseRepoPath uses getProjectName (basename) to compute:
400-
// <srcBaseDir>/<projectName>/.mux-base.git
415+
const layout = buildRemoteProjectLayout("~/mux", "/Users/me/code/my-project");
401416
const result = computeBaseRepoPath("~/mux", "/Users/me/code/my-project");
402-
expect(result).toBe("~/mux/my-project/.mux-base.git");
417+
expect(result).toBe(layout.baseRepoPath);
418+
expect(result).toMatch(/^~\/mux\/my-project-[a-f0-9]{12}\/\.mux-base\.git$/);
403419
});
404420

405421
it("handles absolute srcBaseDir", () => {
422+
const layout = buildRemoteProjectLayout("/home/user/src", "/code/repo");
406423
const result = computeBaseRepoPath("/home/user/src", "/code/repo");
407-
expect(result).toBe("/home/user/src/repo/.mux-base.git");
424+
expect(result).toBe(layout.baseRepoPath);
425+
expect(result).toMatch(/^\/home\/user\/src\/repo-[a-f0-9]{12}\/\.mux-base\.git$/);
408426
});
409427
});

0 commit comments

Comments
 (0)