Skip to content

Commit 6db43b3

Browse files
committed
Fix SSHRuntime to pass abort signals to all exec() calls
Audit revealed several exec() calls in SSHRuntime that weren't respecting abort signals even though they go through SSH: Fixed: - stat(): Now passes abortSignal to exec() (was ignoring it) - syncProjectToRemote(): All exec() calls now pass abortSignal - git remote set-url origin - git remote remove origin - rm bundle cleanup operations - Bundle creation spawn() now handles abort signal - createWorkspace(): mkdir exec() now passes abortSignal - deleteWorkspace(): All exec() calls now pass abortSignal - test -d existence check - git diff uncommitted changes check These operations all go through SSH and can benefit from cancellation, especially bundle creation which can take significant time on large repos.
1 parent ebd6ced commit 6db43b3

File tree

1 file changed

+23
-5
lines changed

1 file changed

+23
-5
lines changed

src/runtime/SSHRuntime.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,13 +292,13 @@ export class SSHRuntime implements Runtime {
292292
/**
293293
* Get file statistics over SSH
294294
*/
295-
async stat(path: string, _abortSignal?: AbortSignal): Promise<FileStat> {
295+
async stat(path: string, abortSignal?: AbortSignal): Promise<FileStat> {
296296
// Use stat with format string to get: size, mtime, type
297297
// %s = size, %Y = mtime (seconds since epoch), %F = file type
298-
// Note: timeout is <10s so no abort signal needed per requirement
299298
const stream = await this.exec(`stat -c '%s %Y %F' ${shescape.quote(path)}`, {
300299
cwd: this.config.srcBaseDir,
301-
timeout: 10, // 10 seconds - stat should be fast (no abort needed per requirement)
300+
timeout: 10,
301+
abortSignal,
302302
});
303303

304304
const [stdout, stderr, exitCode] = await Promise.all([
@@ -519,6 +519,13 @@ export class SSHRuntime implements Runtime {
519519
log.debug(`Creating bundle: ${command}`);
520520
const proc = spawn("bash", ["-c", command]);
521521

522+
// Handle abort signal
523+
const abortHandler = () => {
524+
proc.kill();
525+
reject(new Error("Bundle creation aborted"));
526+
};
527+
abortSignal?.addEventListener("abort", abortHandler);
528+
522529
streamProcessToLogger(proc, initLogger, {
523530
logStdout: false,
524531
logStderr: true,
@@ -530,14 +537,18 @@ export class SSHRuntime implements Runtime {
530537
});
531538

532539
proc.on("close", (code) => {
533-
if (code === 0) {
540+
abortSignal?.removeEventListener("abort", abortHandler);
541+
if (abortSignal?.aborted) {
542+
reject(new Error("Bundle creation aborted"));
543+
} else if (code === 0) {
534544
resolve();
535545
} else {
536546
reject(new Error(`Failed to create bundle: ${stderr}`));
537547
}
538548
});
539549

540550
proc.on("error", (err) => {
551+
abortSignal?.removeEventListener("abort", abortHandler);
541552
reject(err);
542553
});
543554
});
@@ -588,6 +599,7 @@ export class SSHRuntime implements Runtime {
588599
{
589600
cwd: "~",
590601
timeout: 10,
602+
abortSignal,
591603
}
592604
);
593605

@@ -605,6 +617,7 @@ export class SSHRuntime implements Runtime {
605617
{
606618
cwd: "~",
607619
timeout: 10,
620+
abortSignal,
608621
}
609622
);
610623
await removeOriginStream.exitCode;
@@ -615,6 +628,7 @@ export class SSHRuntime implements Runtime {
615628
const rmStream = await this.exec(`rm ${bundleTempPath}`, {
616629
cwd: "~",
617630
timeout: 10,
631+
abortSignal,
618632
});
619633

620634
const rmExitCode = await rmStream.exitCode;
@@ -629,6 +643,7 @@ export class SSHRuntime implements Runtime {
629643
const rmStream = await this.exec(`rm -f ${bundleTempPath}`, {
630644
cwd: "~",
631645
timeout: 10,
646+
abortSignal,
632647
});
633648
await rmStream.exitCode;
634649
} catch {
@@ -719,7 +734,7 @@ export class SSHRuntime implements Runtime {
719734

720735
async createWorkspace(params: WorkspaceCreationParams): Promise<WorkspaceCreationResult> {
721736
try {
722-
const { projectPath, branchName, initLogger } = params;
737+
const { projectPath, branchName, initLogger, abortSignal } = params;
723738
// Compute workspace path using canonical method
724739
const workspacePath = this.getWorkspacePath(projectPath, branchName);
725740

@@ -740,6 +755,7 @@ export class SSHRuntime implements Runtime {
740755
const mkdirStream = await this.exec(parentDirCommand, {
741756
cwd: "/tmp",
742757
timeout: 10,
758+
abortSignal,
743759
});
744760
const mkdirExitCode = await mkdirStream.exitCode;
745761
if (mkdirExitCode !== 0) {
@@ -923,6 +939,7 @@ export class SSHRuntime implements Runtime {
923939
const checkExistStream = await this.exec(`test -d ${shescape.quote(deletedPath)}`, {
924940
cwd: this.config.srcBaseDir,
925941
timeout: 10,
942+
abortSignal,
926943
});
927944

928945
await checkExistStream.stdin.close();
@@ -941,6 +958,7 @@ export class SSHRuntime implements Runtime {
941958
{
942959
cwd: this.config.srcBaseDir,
943960
timeout: 10,
961+
abortSignal,
944962
}
945963
);
946964

0 commit comments

Comments
 (0)