From 0d22f861cb077626a674832964ece795ac3a2d3e Mon Sep 17 00:00:00 2001 From: reuben russell Date: Tue, 1 Sep 2026 13:36:18 +0000 Subject: [PATCH] STR-2927 preserve newer T3 runtime ownership --- apps/server/src/server.ts | 17 ++++--- apps/server/src/serverRuntimeState.test.ts | 58 ++++++++++++++++++++++ apps/server/src/serverRuntimeState.ts | 18 +++++-- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 32bcaaa8b96b..821f3c94e1c4 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -495,7 +495,7 @@ export const makeServerLayer = Layer.unwrap( const server = yield* HttpServer.HttpServer; const address = server.address; if (typeof address === "string" || !("port" in address)) { - return; + return null; } const state = yield* makePersistedServerRuntimeState({ @@ -510,13 +510,16 @@ export const makeServerLayer = Layer.unwrap( Effect.logWarning("Failed to persist server runtime state", { cause }), ), ); + return state; }), - () => - clearPersistedServerRuntimeState(config.serverRuntimeStatePath).pipe( - Effect.catchCause((cause) => - Effect.logWarning("Failed to clear server runtime state", { cause }), - ), - ), + (state) => + state === null + ? Effect.void + : clearPersistedServerRuntimeState(config.serverRuntimeStatePath, state).pipe( + Effect.catchCause((cause) => + Effect.logWarning("Failed to clear server runtime state", { cause }), + ), + ), ), ); const tailscaleServeLayer = config.tailscaleServeEnabled diff --git a/apps/server/src/serverRuntimeState.test.ts b/apps/server/src/serverRuntimeState.test.ts index 4c2375b29a74..9858d7aa687e 100644 --- a/apps/server/src/serverRuntimeState.test.ts +++ b/apps/server/src/serverRuntimeState.test.ts @@ -78,6 +78,64 @@ describe("serverRuntimeState", () => { }).pipe(Effect.provide(NodeServices.layer)), ); + it.effect("clears runtime state owned by the stopping server", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const root = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-server-runtime-state-test-", + }); + const statePath = path.join(root, "server.json"); + const state: ServerRuntimeState.PersistedServerRuntimeState = { + version: 1, + pid: 123, + port: 4_971, + origin: "http://127.0.0.1:4971", + startedAt: "2026-06-20T00:00:00.000Z", + }; + yield* ServerRuntimeState.persistServerRuntimeState({ path: statePath, state }); + + yield* ServerRuntimeState.clearPersistedServerRuntimeState(statePath, state); + + const restored = yield* ServerRuntimeState.readPersistedServerRuntimeState(statePath); + assert.isTrue(Option.isNone(restored)); + }).pipe(Effect.provide(NodeServices.layer)), + ); + + it.effect("does not clear runtime state replaced by a newer server", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const root = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-server-runtime-state-test-", + }); + const statePath = path.join(root, "server.json"); + const oldState: ServerRuntimeState.PersistedServerRuntimeState = { + version: 1, + pid: 123, + port: 4_971, + origin: "http://127.0.0.1:4971", + startedAt: "2026-06-20T00:00:00.000Z", + }; + const currentState: ServerRuntimeState.PersistedServerRuntimeState = { + ...oldState, + pid: 456, + port: 4_972, + origin: "http://127.0.0.1:4972", + startedAt: "2026-06-20T00:01:00.000Z", + }; + yield* ServerRuntimeState.persistServerRuntimeState({ + path: statePath, + state: currentState, + }); + + yield* ServerRuntimeState.clearPersistedServerRuntimeState(statePath, oldState); + + const restored = yield* ServerRuntimeState.readPersistedServerRuntimeState(statePath); + assert.deepEqual(Option.getOrThrow(restored), currentState); + }).pipe(Effect.provide(NodeServices.layer)), + ); + it.effect("preserves malformed state decode failures", () => { const logs: CapturedLog[] = []; const logger = Logger.make(({ fiber, message }) => { diff --git a/apps/server/src/serverRuntimeState.ts b/apps/server/src/serverRuntimeState.ts index b32f3814547c..415ecca30c83 100644 --- a/apps/server/src/serverRuntimeState.ts +++ b/apps/server/src/serverRuntimeState.ts @@ -79,8 +79,19 @@ export const persistServerRuntimeState = (input: { ), ); -export const clearPersistedServerRuntimeState = (path: string) => - Effect.gen(function* () { +export const clearPersistedServerRuntimeState = Effect.fn("clearPersistedServerRuntimeState")( + function* (path: string, expectedOwner?: Pick) { + if (expectedOwner) { + const current = yield* readPersistedServerRuntimeState(path); + if ( + Option.isNone(current) || + current.value.pid !== expectedOwner.pid || + current.value.startedAt !== expectedOwner.startedAt + ) { + return; + } + } + const fs = yield* FileSystem.FileSystem; yield* fs.remove(path, { force: true }).pipe( Effect.mapError( @@ -102,7 +113,8 @@ export const clearPersistedServerRuntimeState = (path: string) => ), }), ); - }); + }, +); export const readPersistedServerRuntimeState = (path: string) => Effect.gen(function* () {