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
49 changes: 49 additions & 0 deletions packages/ssh/src/tunnel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,55 @@ describe("ssh tunnel scripts", () => {
}),
);

it.effect("connects through a tunnel whose responses take longer than a second", () =>
Effect.gen(function* () {
// Over a ~1s RTT link each request through the forward takes a few round trips.
const RESPONSE_MS = 2_500;
const tunnelSpawned = yield* Deferred.make<void>();
const spawner = ChildProcessSpawner.make((command) =>
commandArgs(command).includes("-N")
? Deferred.succeed(tunnelSpawned, undefined).pipe(
Effect.as(makeRunningProcess(() => undefined)),
)
: Effect.succeed(makeSuccessfulProcess('{"remotePort":3773}\n')),
);
const httpClient = HttpClient.make((request) =>
Effect.sleep(Duration.millis(RESPONSE_MS)).pipe(
Effect.as(HttpClientResponse.fromWeb(request, new Response("", { status: 200 }))),
),
);
const layer = Layer.mergeAll(
NodeServices.layer,
NodeCrypto.layer,
Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
Layer.succeed(HttpClient.HttpClient, httpClient),
Layer.succeed(NetService.NetService, testNetService),
SshAuth.SshPasswordPrompt.disabledLayer,
SshTunnel.SshEnvironmentManager.layer({ resolveCliRunner: Effect.succeed(ARCHIVE) }),
);
yield* Effect.gen(function* () {
const manager = yield* SshTunnel.SshEnvironmentManager;
const ensure = yield* Effect.forkChild(
Effect.result(
manager.ensureEnvironment({
alias: "devbox",
hostname: "devbox",
username: null,
port: null,
}),
),
);
yield* Deferred.await(tunnelSpawned);
yield* TestClock.adjust(Duration.seconds(60));
const result = yield* Fiber.join(ensure);
assert.isTrue(
Result.isSuccess(result),
Result.isFailure(result) ? result.failure.message : undefined,
);
}).pipe(Effect.provide(layer), Effect.scoped);
}),
);

it.effect("reconnects a saved alias through the port its ssh config names now", () =>
Effect.gen(function* () {
let configPort = 53_791;
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 @@ -51,6 +51,8 @@ const DEFAULT_REMOTE_PORT = 3773;
const REMOTE_PORT_SCAN_WINDOW = 200;
const SSH_READY_TIMEOUT_MS = 20_000;
const SSH_READY_PROBE_TIMEOUT_MS = 1_000;
// Probes through the forward pay several link round trips, about 4s at a 1s RTT.
const SSH_TUNNEL_READY_PROBE_TIMEOUT_MS = 5_000;
const TUNNEL_SHUTDOWN_TIMEOUT_MS = 2_000;
const TUNNEL_STDERR_TAIL_CHARS = 16_384;
const REMOTE_READY_TIMEOUT_MS = 60_000;
Expand Down Expand Up @@ -1159,7 +1161,7 @@ const waitForHttpReady = (input: {
...(input.path === undefined ? {} : { path: input.path }),
...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
...(input.intervalMs === undefined ? {} : { intervalMs: input.intervalMs }),
probeTimeoutMs: input.probeTimeoutMs ?? SSH_READY_PROBE_TIMEOUT_MS,
probeTimeoutMs: input.probeTimeoutMs ?? SSH_TUNNEL_READY_PROBE_TIMEOUT_MS,
makeError: ({ requestUrl, probeTimeoutMs, cause }) => {
if (typeof cause === "object" && cause !== null && "kind" in cause) {
const kind = (cause as { readonly kind?: unknown }).kind;
Expand Down
Loading