Skip to content

Commit b2d7a68

Browse files
committed
refactor: deduplicate sleepWithAbort into shared abort utility
Extract the identical sleepWithAbort function from SSHRuntime, sshConnectionPool, SSH2ConnectionPool, and codexOauthService into src/node/utils/abort.ts (which already hosts AbortSignal helpers). The four copies had trivial cosmetic differences (error message text, optional vs required signal param) but identical behavior. The shared version uses optional AbortSignal and 'Operation aborted' error text, matching the convention used by the majority of callers.
1 parent c22d062 commit b2d7a68

File tree

5 files changed

+38
-109
lines changed

5 files changed

+38
-109
lines changed

src/node/runtime/SSH2ConnectionPool.ts

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Duplex } from "stream";
1515
import type { Client } from "ssh2";
1616
import { getErrorMessage } from "@/common/utils/errors";
1717
import { log } from "@/node/services/log";
18+
import { sleepWithAbort } from "@/node/utils/abort";
1819
import { attachStreamErrorHandler } from "@/node/utils/streamErrors";
1920
import type { SSHConnectionConfig, ConnectionHealth } from "./sshConnectionPool";
2021
import { resolveSSHConfig, type ResolvedSSHConfig } from "./sshConfigParser";
@@ -84,32 +85,6 @@ function withJitter(seconds: number): number {
8485
return seconds * jitterFactor;
8586
}
8687

87-
async function sleepWithAbort(ms: number, abortSignal?: AbortSignal): Promise<void> {
88-
if (ms <= 0) return;
89-
if (abortSignal?.aborted) {
90-
throw new Error("Operation aborted");
91-
}
92-
93-
await new Promise<void>((resolve, reject) => {
94-
const timer = setTimeout(() => {
95-
cleanup();
96-
resolve();
97-
}, ms);
98-
99-
const onAbort = () => {
100-
cleanup();
101-
reject(new Error("Operation aborted"));
102-
};
103-
104-
const cleanup = () => {
105-
clearTimeout(timer);
106-
abortSignal?.removeEventListener("abort", onAbort);
107-
};
108-
109-
abortSignal?.addEventListener("abort", onAbort);
110-
});
111-
}
112-
11388
function getAgentConfig(): string | undefined {
11489
if (process.env.SSH_AUTH_SOCK) {
11590
return process.env.SSH_AUTH_SOCK;

src/node/runtime/SSHRuntime.ts

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { RemoteRuntime, type SpawnResult } from "./RemoteRuntime";
3535
import { log } from "@/node/services/log";
3636
import { runInitHookOnRuntime, runWorkspaceInitHook } from "./initHook";
3737
import { expandTildeForSSH, cdCommandForSSH } from "./tildeExpansion";
38+
import { sleepWithAbort } from "@/node/utils/abort";
3839
import { execBuffered } from "@/node/utils/runtime/helpers";
3940
import { getErrorMessage } from "@/common/utils/errors";
4041
import {
@@ -129,34 +130,6 @@ async function enqueueProjectSync(
129130
}
130131
}
131132

132-
async function sleepWithAbort(ms: number, abortSignal?: AbortSignal): Promise<void> {
133-
if (ms <= 0) {
134-
return;
135-
}
136-
if (abortSignal?.aborted) {
137-
throw new Error("Operation aborted");
138-
}
139-
140-
await new Promise<void>((resolve, reject) => {
141-
const timer = setTimeout(() => {
142-
cleanup();
143-
resolve();
144-
}, ms);
145-
146-
const onAbort = () => {
147-
cleanup();
148-
reject(new Error("Operation aborted"));
149-
};
150-
151-
const cleanup = () => {
152-
clearTimeout(timer);
153-
abortSignal?.removeEventListener("abort", onAbort);
154-
};
155-
156-
abortSignal?.addEventListener("abort", onAbort, { once: true });
157-
});
158-
}
159-
160133
function isGitConfigLockConflict(message: string): boolean {
161134
return /could not lock config file/i.test(message);
162135
}

src/node/runtime/sshConnectionPool.ts

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as crypto from "crypto";
1818
import * as path from "path";
1919
import * as os from "os";
2020
import { spawn } from "child_process";
21+
import { sleepWithAbort } from "@/node/utils/abort";
2122
import { HOST_KEY_APPROVAL_TIMEOUT_MS } from "@/common/constants/ssh";
2223
import { formatSshEndpoint } from "@/common/utils/ssh/formatSshEndpoint";
2324
import { log } from "@/node/services/log";
@@ -152,32 +153,6 @@ export interface AcquireConnectionOptions {
152153
sleep?: (ms: number, abortSignal?: AbortSignal) => Promise<void>;
153154
}
154155

155-
async function sleepWithAbort(ms: number, abortSignal?: AbortSignal): Promise<void> {
156-
if (ms <= 0) return;
157-
if (abortSignal?.aborted) {
158-
throw new Error("Operation aborted");
159-
}
160-
161-
await new Promise<void>((resolve, reject) => {
162-
const timer = setTimeout(() => {
163-
cleanup();
164-
resolve();
165-
}, ms);
166-
167-
const onAbort = () => {
168-
cleanup();
169-
reject(new Error("Operation aborted"));
170-
};
171-
172-
const cleanup = () => {
173-
clearTimeout(timer);
174-
abortSignal?.removeEventListener("abort", onAbort);
175-
};
176-
177-
abortSignal?.addEventListener("abort", onAbort);
178-
});
179-
}
180-
181156
async function waitForPromiseWithTimeout<T>(
182157
promise: Promise<T>,
183158
timeoutMs: number,

src/node/services/codexOauthService.ts

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type { Config } from "@/node/config";
1616
import type { ProviderService } from "@/node/services/providerService";
1717
import type { WindowService } from "@/node/services/windowService";
1818
import { log } from "@/node/services/log";
19+
import { sleepWithAbort } from "@/node/utils/abort";
1920
import { AsyncMutex } from "@/node/utils/concurrency/asyncMutex";
2021
import {
2122
extractAccountIdFromTokens,
@@ -729,32 +730,3 @@ export class CodexOauthService {
729730
return Promise.resolve();
730731
}
731732
}
732-
733-
async function sleepWithAbort(ms: number, signal: AbortSignal): Promise<void> {
734-
if (ms <= 0) {
735-
return;
736-
}
737-
738-
if (signal.aborted) {
739-
throw new Error("aborted");
740-
}
741-
742-
await new Promise<void>((resolve, reject) => {
743-
const timeout = setTimeout(() => {
744-
cleanup();
745-
resolve();
746-
}, ms);
747-
748-
const onAbort = () => {
749-
cleanup();
750-
reject(new Error("aborted"));
751-
};
752-
753-
const cleanup = () => {
754-
clearTimeout(timeout);
755-
signal.removeEventListener("abort", onAbort);
756-
};
757-
758-
signal.addEventListener("abort", onAbort);
759-
});
760-
}

src/node/utils/abort.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,37 @@ export function linkAbortSignal(
3333
source.addEventListener("abort", onAbort, { once: true });
3434
return () => source.removeEventListener("abort", onAbort);
3535
}
36+
37+
/**
38+
* Sleep for `ms` milliseconds, rejecting early if `abortSignal` fires.
39+
*
40+
* Extracted from four identical copies across SSHRuntime, sshConnectionPool,
41+
* SSH2ConnectionPool, and codexOauthService.
42+
*/
43+
export async function sleepWithAbort(ms: number, abortSignal?: AbortSignal): Promise<void> {
44+
if (ms <= 0) {
45+
return;
46+
}
47+
if (abortSignal?.aborted) {
48+
throw new Error("Operation aborted");
49+
}
50+
51+
await new Promise<void>((resolve, reject) => {
52+
const timer = setTimeout(() => {
53+
cleanup();
54+
resolve();
55+
}, ms);
56+
57+
const onAbort = () => {
58+
cleanup();
59+
reject(new Error("Operation aborted"));
60+
};
61+
62+
const cleanup = () => {
63+
clearTimeout(timer);
64+
abortSignal?.removeEventListener("abort", onAbort);
65+
};
66+
67+
abortSignal?.addEventListener("abort", onAbort, { once: true });
68+
});
69+
}

0 commit comments

Comments
 (0)