Skip to content
Open
8 changes: 7 additions & 1 deletion docs/internals/remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ network receives no credential. That check is not proof of a working route:
when every route stays silent, each is still tried. A route that fails to
connect, including a blocked one such as a signed-out T3 Connect, moves on to
the next; only an incompatible server stops the walk, because it is the same
server on every route. While connected over a later route the
server on every route. A direct route that answered the check but is still
connecting when the check's head start ends starts T3 Connect instead of holding
it back. T3 Connect then races that route and every direct route after it, so a
failed direct route still moves on to the next one. The first to connect wins and
the others close. An incompatible T3 Connect server does not end that race, because
relay can reach a different server than a direct route, such as a preview app
sharing the home directory; it is reported only when no direct route connects. While connected over a later route the
[supervisor](../../packages/client-runtime/src/connection/supervisor.ts)
preflights the earlier ones and replaces the session when one would connect.
Preflight includes authorization so a route that answers but rejects this
Expand Down
77 changes: 73 additions & 4 deletions packages/client-runtime/src/connection/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Option from "effect/Option";
import * as Exit from "effect/Exit";
import * as Fiber from "effect/Fiber";
import * as Layer from "effect/Layer";
import type * as Result from "effect/Result";
import * as Scope from "effect/Scope";
import * as FetchHttpClient from "effect/http/FetchHttpClient";
import * as HttpClient from "effect/http/HttpClient";
Expand Down Expand Up @@ -85,8 +86,10 @@ export const connectOverRoutes = Effect.fn("ConnectionDriver.connectOverRoutes")
let transient: ConnectionAttemptError | undefined;
let blocked: ConnectionAttemptError | undefined;
// Each route gets its own scope so a half-open session closes before the next try.
const attempt = Effect.fnUntraced(function* (route: ConnectionRoute) {
const routeScope = yield* Scope.fork(attemptScope);
const attempt = Effect.fnUntraced(function* (
route: ConnectionRoute,
routeScope: Scope.Closeable,
) {
const result = yield* connectRoute(route).pipe(
Scope.provide(routeScope),
Effect.onExit((exit) => (Exit.isSuccess(exit) ? Effect.void : Scope.close(routeScope, exit))),
Expand All @@ -98,8 +101,64 @@ export const connectOverRoutes = Effect.fn("ConnectionDriver.connectOverRoutes")
}
return result;
});
const relayIndex = routes.findIndex((route) => route.target._tag === "RelayConnectionTarget");
const relayRoute = routes[relayIndex];
type Settled = {
readonly result: Result.Result<EnvironmentConnectionLease, ConnectionAttemptError>;
readonly scope: Scope.Closeable;
};
let relay:
| {
readonly fiber: Fiber.Fiber<
Settled,
Result.Result<EnvironmentConnectionLease, ConnectionAttemptError>
>;
readonly scope: Scope.Closeable;
}
| undefined;
// A direct route still connecting when the head start ends starts relay, which
// then races every direct attempt after it. The first to connect wins. A failed
// direct route moves on to the next one while relay keeps connecting.
const attemptRacingRelay = Effect.fnUntraced(function* (route: ConnectionRoute) {
const directScope = yield* Scope.fork(attemptScope);
const direct = yield* Effect.forkChild(attempt(route, directScope));
if (relay === undefined) {
const early = yield* Fiber.join(direct).pipe(Effect.timeoutOption(ROUTE_CHECK_HEAD_START_MS));
if (Option.isSome(early)) return early.value;
const relayScope = yield* Scope.fork(attemptScope);
const fiber = yield* Effect.forkChild(
attempt(relayRoute!, relayScope).pipe(
Effect.flatMap((result) =>
// Relay can reach a different server than the direct routes, so an
// incompatible relay is a failed backup, not the end of the walk.
result._tag === "Success"
? Effect.succeed<Settled>({ result, scope: relayScope })
: Effect.fail(result),
),
),
);
relay = { fiber, scope: relayScope };
}
const racing = relay;
const raced = yield* Effect.race(
Fiber.join(direct).pipe(Effect.map((result): Settled => ({ result, scope: directScope }))),
// A failed relay leaves the direct attempt to finish on its own.
Fiber.join(racing.fiber).pipe(Effect.catch(() => Effect.never)),
);
if (raced.scope === directScope) {
if (raced.result._tag === "Success" || raced.result.failure.reason === "unsupported") {
yield* Fiber.interrupt(racing.fiber);
yield* Scope.close(racing.scope, Exit.void);
}
} else {
yield* Fiber.interrupt(direct);
yield* Scope.close(directScope, Exit.void);
}
return raced.result;
});
const silent: Array<{ readonly route: ConnectionRoute; readonly index: number }> = [];
for (const [index, route] of routes.entries()) {
if (index === relayIndex && relay !== undefined) continue;
const check =
checks.length === 0
? "unchecked"
Expand All @@ -111,11 +170,21 @@ export const connectOverRoutes = Effect.fn("ConnectionDriver.connectOverRoutes")
silent.push({ route, index });
continue;
}
const result = yield* attempt(route);
const result =
relay !== undefined ||
(check === "answered" && relayRoute !== undefined && index < relayIndex)
? yield* attemptRacingRelay(route)
: yield* attempt(route, yield* Scope.fork(attemptScope));
if (result._tag === "Success") return result.success;
// An incompatible server is the same server on every route.
if (result.failure.reason === "unsupported") return yield* result.failure;
}
if (relay !== undefined) {
const settled = yield* Fiber.join(relay.fiber).pipe(Effect.result);
const result = settled._tag === "Success" ? settled.success.result : settled.failure;
if (result._tag === "Success") return result.success;
if (result.failure.reason === "unsupported") return yield* result.failure;
}
const late = yield* Effect.forEach(silent, ({ index }) => Fiber.join(checks[index]!), {
concurrency: "unbounded",
});
Expand All @@ -126,7 +195,7 @@ export const connectOverRoutes = Effect.fn("ConnectionDriver.connectOverRoutes")
// longer than the check did. Once another route failed in a way that
// retries, the retry reaches it sooner than waiting here would.
if (transient !== undefined && stillSilent.some((entry) => entry.route === route)) break;
const result = yield* attempt(route);
const result = yield* attempt(route, yield* Scope.fork(attemptScope));
if (result._tag === "Success") return result.success;
if (result.failure.reason === "unsupported") return yield* result.failure;
}
Expand Down
Loading
Loading