mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-13 15:44:56 +00:00
refactor(instance-store): consolidate dispose helpers (#25424)
This commit is contained in:
@@ -9,7 +9,7 @@ export async function bootstrap<T>(directory: string, cb: () => Promise<T>) {
|
||||
const result = await cb()
|
||||
return result
|
||||
} finally {
|
||||
await InstanceStore.runtime.runPromise((s) => s.dispose(Instance.current))
|
||||
await InstanceStore.disposeInstance(Instance.current)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -88,7 +88,7 @@ export const rpc = {
|
||||
async shutdown() {
|
||||
Log.Default.info("worker shutting down")
|
||||
|
||||
await InstanceStore.runtime.runPromise((s) => s.disposeAll())
|
||||
await InstanceStore.disposeAllInstances()
|
||||
if (server) await server.stop(true)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Env } from "../env"
|
||||
import { applyEdits, modify } from "jsonc-parser"
|
||||
import { type InstanceContext } from "../project/instance"
|
||||
import { InstanceStore } from "../project/instance-store"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { existsSync } from "fs"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
@@ -739,15 +738,17 @@ export const layer = Layer.effect(
|
||||
.writeFileString(file, JSON.stringify(mergeDeep(writable(existing), writable(config)), null, 2))
|
||||
.pipe(Effect.orDie)
|
||||
if (options?.dispose !== false) {
|
||||
const ctx = yield* InstanceRef
|
||||
if (ctx) yield* Effect.promise(() => InstanceStore.runtime.runPromise((s) => s.dispose(ctx)))
|
||||
// Fail loudly if no instance is bound — silently skipping would
|
||||
// mask "config update without an active instance" bugs. The throw
|
||||
// comes from `Instance.current` inside `InstanceState.context`.
|
||||
const ctx = yield* InstanceState.context
|
||||
yield* Effect.promise(() => InstanceStore.disposeInstance(ctx))
|
||||
}
|
||||
})
|
||||
|
||||
const invalidate = Effect.fn("Config.invalidate")(function* (wait?: boolean) {
|
||||
yield* invalidateGlobal
|
||||
const task = InstanceStore.runtime
|
||||
.runPromise((s) => s.disposeAll())
|
||||
const task = InstanceStore.disposeAllInstances()
|
||||
.catch(() => undefined)
|
||||
.finally(() =>
|
||||
GlobalBus.emit("event", {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { disposeInstance } from "@/effect/instance-registry"
|
||||
import { disposeInstance as runDisposers } from "@/effect/instance-registry"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Context, Deferred, Duration, Effect, Exit, Layer, Scope } from "effect"
|
||||
@@ -94,7 +94,7 @@ export const layer: Layer.Layer<Service, never, Project.Service> = Layer.effect(
|
||||
|
||||
const disposeContext = Effect.fn("InstanceStore.disposeContext")(function* (ctx: InstanceContext) {
|
||||
yield* Effect.logInfo("disposing instance", { directory: ctx.directory })
|
||||
yield* Effect.promise(() => disposeInstance(ctx.directory))
|
||||
yield* Effect.promise(() => runDisposers(ctx.directory))
|
||||
yield* emitDisposed({ directory: ctx.directory, project: ctx.project.id })
|
||||
})
|
||||
|
||||
@@ -135,7 +135,7 @@ export const layer: Layer.Layer<Service, never, Project.Service> = Layer.effect(
|
||||
yield* Effect.logInfo("reloading instance", { directory })
|
||||
if (previous) {
|
||||
yield* Deferred.await(previous.deferred).pipe(Effect.ignore)
|
||||
yield* Effect.promise(() => disposeInstance(directory))
|
||||
yield* Effect.promise(() => runDisposers(directory))
|
||||
yield* emitDisposed({ directory, project: input.project?.id })
|
||||
}
|
||||
yield* completeLoad(directory, input, entry)
|
||||
@@ -197,4 +197,11 @@ export const defaultLayer = layer.pipe(Layer.provide(Project.defaultLayer))
|
||||
|
||||
export const runtime = makeRuntime(Service, defaultLayer)
|
||||
|
||||
// Promise-returning helpers for callers without an Effect runtime in scope.
|
||||
// They route through `runtime` (not a yielded Service from a fresh runtime)
|
||||
// so they share the cache that `Instance.provide` populates.
|
||||
export const disposeInstance = (ctx: InstanceContext) => runtime.runPromise((store) => store.dispose(ctx))
|
||||
export const disposeAllInstances = () => runtime.runPromise((store) => store.disposeAll())
|
||||
export const reloadInstance = (input: LoadInput) => runtime.runPromise((store) => store.reload(input))
|
||||
|
||||
export * as InstanceStore from "./instance-store"
|
||||
|
||||
@@ -42,10 +42,17 @@ export const Instance = {
|
||||
restore<R>(ctx: InstanceContext, fn: () => R): R {
|
||||
return context.provide(ctx, fn)
|
||||
},
|
||||
// followup: `reload` survives because `test/server/project-init-git.test.ts`
|
||||
// spies on this exact method. Once that test asserts on `InstanceStore.reloadInstance`
|
||||
// (or moves to an Effect runtime), this wrapper can drop.
|
||||
async reload(input: InstanceStore.LoadInput) {
|
||||
return InstanceStore.runtime.runPromise((store) => store.reload(input))
|
||||
return InstanceStore.reloadInstance(input)
|
||||
},
|
||||
// followup: `dispose` survives for legacy fixtures that read `Instance.current`
|
||||
// out of ALS (e.g. `test/fixture/fixture.ts` `provideTmpdirInstance`,
|
||||
// `test/question/question.test.ts` cancellation tests). Convert those to call
|
||||
// `InstanceStore.disposeInstance(ctx)` directly once `Instance.provide` is gone.
|
||||
async dispose() {
|
||||
return InstanceStore.runtime.runPromise((store) => store.dispose(Instance.current))
|
||||
return InstanceStore.disposeInstance(Instance.current)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ export const GlobalRoutes = lazy(() =>
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
await InstanceStore.runtime.runPromise((s) => s.disposeAll())
|
||||
await InstanceStore.disposeAllInstances()
|
||||
GlobalBus.emit("event", {
|
||||
directory: "global",
|
||||
payload: {
|
||||
|
||||
@@ -63,7 +63,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
await InstanceStore.runtime.runPromise((s) => s.dispose(Instance.current))
|
||||
await InstanceStore.disposeInstance(Instance.current)
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -81,7 +81,11 @@ export const ProjectRoutes = lazy(() =>
|
||||
Project.Service.use((svc) => svc.initGit({ directory: dir, project: prev })),
|
||||
)
|
||||
if (next.id === prev.id && next.vcs === prev.vcs && next.worktree === prev.worktree) return c.json(next)
|
||||
await Instance.reload({ directory: dir, worktree: dir, project: next })
|
||||
await Instance.reload({
|
||||
directory: dir,
|
||||
worktree: dir,
|
||||
project: next,
|
||||
})
|
||||
return c.json(next)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -9,15 +9,11 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import type { Config } from "@/config/config"
|
||||
import { InstanceRef } from "../../src/effect/instance-ref"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { InstanceStore } from "../../src/project/instance-store"
|
||||
import { TestLLMServer } from "../lib/llm-server"
|
||||
|
||||
// Test helper for tearing down all loaded instances. Used in afterEach hooks.
|
||||
// Replaces direct Instance.disposeAll() calls so the legacy promise method can be removed.
|
||||
// IMPORTANT: must use InstanceStore.runtime, not AppRuntime or a test-layer Service —
|
||||
// Instance.provide loads instances into InstanceStore.runtime's Service cache, and that
|
||||
// Service is built per-runtime (not shared via memoMap across Effect.runPromise boundaries).
|
||||
export const disposeAllInstances = () => InstanceStore.runtime.runPromise((s) => s.disposeAll())
|
||||
// Re-export for test ergonomics. The implementation lives next to the runtime
|
||||
// it consumes; see `InstanceStore.disposeAllInstances` for the rationale.
|
||||
export { disposeAllInstances } from "../../src/project/instance-store"
|
||||
|
||||
// Strip null bytes from paths (defensive fix for CI environment issues)
|
||||
function sanitizePath(p: string): string {
|
||||
|
||||
Reference in New Issue
Block a user