Skip to content

Commit 1b146ad

Browse files
committed
refactor: replace disposeAll dedup slot with cachedWithTTL
The manual Deferred slot + uninterruptibleMask + identity check collapses into Effect.cachedWithTTL(_, Duration.zero): concurrent callers share the in-flight execution, and the cache expires on completion so the next call runs fresh. Adds a test pinning the re-arm semantic.
1 parent 8a63cbe commit 1b146ad

2 files changed

Lines changed: 42 additions & 36 deletions

File tree

packages/opencode/src/project/instance-store.ts

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { WorkspaceContext } from "@/control-plane/workspace-context"
33
import { disposeInstance } from "@/effect/instance-registry"
44
import { makeRuntime } from "@/effect/run-service"
55
import { AppFileSystem } from "@opencode-ai/core/filesystem"
6-
import { Context, Deferred, Effect, Exit, Layer, Scope } from "effect"
6+
import { Context, Deferred, Duration, Effect, Exit, Layer, Scope } from "effect"
77
import { context, type InstanceContext } from "./instance-context"
88
import * as Project from "./project"
99

@@ -33,9 +33,6 @@ export const layer: Layer.Layer<Service, never, Project.Service> = Layer.effect(
3333
const project = yield* Project.Service
3434
const scope = yield* Scope.Scope
3535
const cache = new Map<string, Entry>()
36-
const disposal = {
37-
all: undefined as Deferred.Deferred<void> | undefined,
38-
}
3936

4037
const boot = Effect.fn("InstanceStore.boot")(function* (input: LoadInput & { directory: string }) {
4138
const ctx =
@@ -146,41 +143,29 @@ export const layer: Layer.Layer<Service, never, Project.Service> = Layer.effect(
146143
yield* disposeEntry(ctx.directory, entry, ctx).pipe(Effect.asVoid)
147144
})
148145

149-
const disposeAll = Effect.fn("InstanceStore.disposeAll")(function* () {
150-
return yield* Effect.uninterruptibleMask((restore) =>
151-
Effect.gen(function* () {
152-
const existing = disposal.all
153-
if (existing) return yield* restore(Deferred.await(existing))
154-
155-
const done = Deferred.makeUnsafe<void>()
156-
const entries = [...cache.entries()]
157-
disposal.all = done
158-
const exit = yield* Effect.gen(function* () {
159-
yield* Effect.logInfo("disposing all instances")
160-
yield* Effect.forEach(
161-
entries,
162-
(item) =>
163-
Effect.gen(function* () {
164-
const exit = yield* Deferred.await(item[1].deferred).pipe(Effect.exit)
165-
if (Exit.isFailure(exit)) {
166-
yield* Effect.logWarning("instance dispose failed", { key: item[0], cause: exit.cause })
167-
yield* removeEntry(item[0], item[1])
168-
return
169-
}
170-
yield* disposeEntry(item[0], item[1], exit.value)
171-
}),
172-
{ discard: true },
173-
)
174-
}).pipe(Effect.exit)
175-
yield* Deferred.done(done, exit).pipe(Effect.asVoid)
176-
if (disposal.all === done) {
177-
disposal.all = undefined
178-
}
179-
return yield* restore(Deferred.await(done))
180-
}),
146+
const disposeAllOnce = Effect.fnUntraced(function* () {
147+
yield* Effect.logInfo("disposing all instances")
148+
yield* Effect.forEach(
149+
[...cache.entries()],
150+
(item) =>
151+
Effect.gen(function* () {
152+
const exit = yield* Deferred.await(item[1].deferred).pipe(Effect.exit)
153+
if (Exit.isFailure(exit)) {
154+
yield* Effect.logWarning("instance dispose failed", { key: item[0], cause: exit.cause })
155+
yield* removeEntry(item[0], item[1])
156+
return
157+
}
158+
yield* disposeEntry(item[0], item[1], exit.value)
159+
}),
160+
{ discard: true },
181161
)
182162
})
183163

164+
const cachedDisposeAll = yield* Effect.cachedWithTTL(disposeAllOnce(), Duration.zero)
165+
const disposeAll = Effect.fn("InstanceStore.disposeAll")(function* () {
166+
return yield* cachedDisposeAll
167+
})
168+
184169
yield* Effect.addFinalizer(() => disposeAll().pipe(Effect.ignore))
185170

186171
return Service.of({

packages/opencode/test/project/instance.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,27 @@ describe("InstanceStore", () => {
215215
}),
216216
)
217217

218+
it.live("re-arms disposeAll after completion", () =>
219+
Effect.gen(function* () {
220+
const dir1 = yield* tmpdirScoped({ git: true })
221+
const dir2 = yield* tmpdirScoped({ git: true })
222+
const store = yield* InstanceStore.Service
223+
const disposed: Array<string> = []
224+
const off = registerDisposer(async (directory) => {
225+
disposed.push(directory)
226+
})
227+
yield* Effect.addFinalizer(() => Effect.sync(off))
228+
229+
yield* store.load({ directory: dir1 })
230+
yield* store.disposeAll()
231+
expect(disposed).toEqual([dir1])
232+
233+
yield* store.load({ directory: dir2 })
234+
yield* store.disposeAll()
235+
expect(disposed).toEqual([dir1, dir2])
236+
}),
237+
)
238+
218239
it.live("keeps Instance.provide as the legacy ALS wrapper", () =>
219240
Effect.gen(function* () {
220241
const dir = yield* tmpdirScoped({ git: true })

0 commit comments

Comments
 (0)