From 0c88e2ab80b15b34fb7b859c10caba3e4bb48d2f Mon Sep 17 00:00:00 2001 From: Maxwell Young Date: Sun, 25 Oct 2026 20:37:28 +1300 Subject: [PATCH] fix(server): release retired provider scopes --- .../provider/ProviderInstanceRegistry.test.ts | 78 +++++++++++++++++++ .../src/provider/ProviderInstanceRegistry.ts | 3 +- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/ProviderInstanceRegistry.test.ts b/apps/server/src/provider/ProviderInstanceRegistry.test.ts index 0315cb8dfd83d..fbf7f3c6568c9 100644 --- a/apps/server/src/provider/ProviderInstanceRegistry.test.ts +++ b/apps/server/src/provider/ProviderInstanceRegistry.test.ts @@ -19,6 +19,8 @@ import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderS import * as HostProcess from "@t3tools/shared/HostProcess"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Scope from "effect/Scope"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; @@ -35,6 +37,7 @@ import * as ModelManifest from "./ModelManifest.ts"; import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts"; import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import { makeProviderInstanceRegistry } from "./ProviderInstanceRegistry.ts"; +import { ProviderDriverError } from "./Errors.ts"; import * as ProviderOrchestrationAdapterInfrastructure from "./ProviderOrchestrationAdapterInfrastructure.ts"; import * as ProviderHostLive from "./ProviderHostLive.ts"; @@ -194,6 +197,81 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => { Layer.provideMerge(layerBase), ); + it.effect.each(["replacement", "removal", "creation failure"] as const)( + "detaches retired provider scopes after repeated %s", + (scenario) => + Effect.gen(function* () { + const parent = yield* Scope.make(); + const fileSystem = yield* FileSystem.FileSystem; + const homePath = yield* fileSystem.makeTempDirectoryScoped(); + let creates = 0; + let closes = 0; + const driver: typeof CodexDriver = { + ...CodexDriver, + create: Effect.fn("ProviderInstanceRegistry.test.create")(function* ( + input: Parameters[0], + ) { + creates++; + yield* Effect.addFinalizer(() => + Effect.sync(() => { + closes++; + }), + ); + if (input.config.binaryPath === "creation-failure") { + return yield* new ProviderDriverError({ + driver: CodexDriver.driverKind, + instanceId: input.instanceId, + detail: "fixture creation failed", + }); + } + return yield* CodexDriver.create(input); + }), + }; + const siblingId = ProviderInstanceId.make("sibling"); + const retiredId = ProviderInstanceId.make("retired"); + const entry = { + driver: CodexDriver.driverKind, + enabled: false, + config: makeCodexConfig({ homePath }), + }; + yield* Effect.gen(function* () { + const { registry, mutator } = yield* makeProviderInstanceRegistry({ + drivers: [driver], + configMap: { [siblingId]: entry }, + }); + const sibling = yield* registry.getInstance(siblingId); + const finalizerCount = () => { + const state = parent.state; + return state._tag === "Open" ? (state.finalizers?.size ?? 1) : 0; + }; + const baseline = finalizerCount(); + for (let index = 0; index < 20; index++) { + yield* mutator.reconcile({ + [siblingId]: entry, + [retiredId]: { + ...entry, + config: makeCodexConfig({ + homePath, + binaryPath: scenario === "creation failure" ? "creation-failure" : "unused-codex", + customModels: [`revision-${index}`], + }), + }, + }); + if (scenario === "removal") yield* mutator.reconcile({ [siblingId]: entry }); + } + yield* mutator.reconcile({ [siblingId]: entry }); + expect(yield* registry.getInstance(siblingId)).toBe(sibling); + expect(closes).toBe(creates - 1); + expect(parent.state._tag).toBe("Open"); + expect(finalizerCount()).toBe(baseline); + }).pipe( + Effect.provideService(Scope.Scope, parent), + Effect.ensuring(Scope.close(parent, Exit.void)), + ); + expect(closes).toBe(creates); + }).pipe(Effect.provide(layerTest)), + ); + it.live("treats an explicit in-config enabled:false as disabling despite the envelope", () => Effect.gen(function* () { const staleId = ProviderInstanceId.make("codex_stale"); diff --git a/apps/server/src/provider/ProviderInstanceRegistry.ts b/apps/server/src/provider/ProviderInstanceRegistry.ts index 3bbbe2900b9f2..d02956ab427b0 100644 --- a/apps/server/src/provider/ProviderInstanceRegistry.ts +++ b/apps/server/src/provider/ProviderInstanceRegistry.ts @@ -109,8 +109,7 @@ const buildEntry = Effect.fn("ProviderInstanceRegistry.buildEntry")(function*