Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions packages/ssh/src/tunnel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ const makeSuccessfulProcess = (stdout: string) => {
});
};

const makeDelayedSuccessfulProcess = (stdout: string, delayMs: number) => {
const process = makeSuccessfulProcess(stdout);
return {
...process,
exitCode: Effect.sleep(Duration.millis(delayMs)).pipe(
Effect.as(ChildProcessSpawner.ExitCode(0)),
),
};
};

const makeRunningProcess = (onKill: () => void) => {
let finish: ((exitCode: ChildProcessSpawner.ExitCode) => void) | null = null;
return ChildProcessSpawner.makeHandle({
Expand Down Expand Up @@ -173,6 +183,7 @@ describe("ssh tunnel scripts", () => {
assert.include(buildRemoteLaunchScript(), '--base-dir "$DEFAULT_SERVER_HOME"');
assert.notInclude(buildRemoteLaunchScript(), "server-home");
assert.include(buildRemoteLaunchScript(), "Remote T3 server did not become ready");
assert.include(buildRemoteLaunchScript(), 'wait_ready "60000"');
assert.include(buildRemoteLaunchScript({ packageSpec: "t3@nightly" }), "t3@nightly");
assert.include(
buildRemotePairingScript(target),
Expand Down Expand Up @@ -234,6 +245,29 @@ describe("ssh tunnel scripts", () => {
}).pipe(Effect.provide(processLayer));
});

it.effect("allows cold remote launches to exceed the default SSH command timeout", () => {
const target = {
alias: "devbox",
hostname: "devbox.example.com",
username: "julius",
port: 2222,
} as const;
const spawner = ChildProcessSpawner.make(() =>
Effect.succeed(makeDelayedSuccessfulProcess('{"remotePort":3774}\n', 75_000)),
);
const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);
const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer());

return Effect.gen(function* () {
const fiber = yield* Effect.forkChild(launchOrReuseRemoteServer(target));
yield* Effect.yieldNow;
yield* TestClock.adjust(Duration.seconds(75));

const result = yield* Fiber.join(fiber);
assert.equal(result.remotePort, 3774);
}).pipe(Effect.provide(processLayer));
});

it("allows the remote port picker to run without a state file path", () => {
assert.include(REMOTE_PICK_PORT_SCRIPT, 'const filePath = process.argv[2] ?? "";');
});
Expand Down
4 changes: 3 additions & 1 deletion packages/ssh/src/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ const REMOTE_PORT_SCAN_WINDOW = 200;
const SSH_READY_TIMEOUT_MS = 20_000;
const SSH_READY_PROBE_TIMEOUT_MS = 1_000;
const TUNNEL_SHUTDOWN_TIMEOUT_MS = 2_000;
const REMOTE_READY_TIMEOUT_MS = 15_000;
const REMOTE_READY_TIMEOUT_MS = 60_000;
const REMOTE_LAUNCH_TIMEOUT_MS = 90_000;
const REMOTE_REUSE_READY_TIMEOUT_MS = 2_000;

export interface RemoteT3RunnerOptions {
Expand Down Expand Up @@ -705,6 +706,7 @@ export const launchOrReuseRemoteServer = Effect.fn("ssh/tunnel.launchOrReuseRemo
const result = yield* runSshCommand(target, {
remoteCommandArgs: ["sh", "-s", "--", remoteStateKey(target)],
stdin: buildRemoteLaunchScript(runner),
timeoutMs: REMOTE_LAUNCH_TIMEOUT_MS,
...(input?.authSecret === undefined ? {} : { authSecret: input.authSecret }),
...(input?.batchMode === undefined ? {} : { batchMode: input.batchMode }),
...(input?.interactiveAuth === undefined ? {} : { interactiveAuth: input.interactiveAuth }),
Expand Down
Loading