Bladeren bron

refactor(cli): dispose bootstrap instance explicitly (#27721)

Shoubhit Dash 3 maanden geleden
bovenliggende
commit
bf64f8cbb5
2 gewijzigde bestanden met toevoegingen van 31 en 14 verwijderingen
  1. 7 13
      packages/opencode/src/cli/bootstrap.ts
  2. 24 1
      packages/opencode/test/project/instance-bootstrap.test.ts

+ 7 - 13
packages/opencode/src/cli/bootstrap.ts

@@ -1,17 +1,11 @@
-import { Instance } from "../project/instance"
 import { InstanceRuntime } from "../project/instance-runtime"
-import { WithInstance } from "../project/with-instance"
+import { context } from "../project/instance-context"
 
 export async function bootstrap<T>(directory: string, cb: () => Promise<T>) {
-  return WithInstance.provide({
-    directory,
-    fn: async () => {
-      try {
-        const result = await cb()
-        return result
-      } finally {
-        await InstanceRuntime.disposeInstance(Instance.current)
-      }
-    },
-  })
+  const ctx = await InstanceRuntime.load({ directory })
+  try {
+    return await context.provide(ctx, cb)
+  } finally {
+    await InstanceRuntime.disposeInstance(ctx)
+  }
 }

+ 24 - 1
packages/opencode/test/project/instance-bootstrap.test.ts

@@ -3,12 +3,13 @@ import { existsSync } from "node:fs"
 import path from "node:path"
 import { pathToFileURL } from "node:url"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
-import { Effect, Layer } from "effect"
+import { Cause, Effect, Exit, Fiber, Layer } from "effect"
 import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap"
 import { InstanceLayer } from "../../src/project/instance-layer"
 import { InstanceStore } from "../../src/project/instance-store"
 import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
+import { waitGlobalBusEvent } from "../server/global-bus"
 
 const it = testEffect(Layer.mergeAll(InstanceLayer.layer, CrossSpawnSpawner.defaultLayer))
 
@@ -54,6 +55,13 @@ const bootstrapFixture = Effect.gen(function* () {
   return { directory: dir, marker }
 })
 
+function waitDisposed(directory: string) {
+  return waitGlobalBusEvent({
+    message: "timed out waiting for CLI bootstrap instance disposal",
+    predicate: (event) => event.payload.type === "server.instance.disposed" && event.directory === directory,
+  })
+}
+
 it.live("InstanceStore.provide runs InstanceBootstrap before effect", () =>
   Effect.gen(function* () {
     const tmp = yield* bootstrapFixture
@@ -75,6 +83,21 @@ it.live("CLI bootstrap runs InstanceBootstrap before callback", () =>
   }),
 )
 
+it.live("CLI bootstrap disposes the instance when the callback rejects", () =>
+  Effect.gen(function* () {
+    const tmp = yield* bootstrapFixture
+    const disposed = yield* waitDisposed(tmp.directory).pipe(Effect.forkScoped)
+
+    const exit = yield* Effect.promise(() => cliBootstrap(tmp.directory, async () => Promise.reject(new Error("boom")))).pipe(
+      Effect.exit,
+    )
+
+    expect(Exit.isFailure(exit)).toBe(true)
+    if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toMatchObject({ message: "boom" })
+    yield* Fiber.join(disposed)
+  }),
+)
+
 it.live("InstanceStore.reload runs InstanceBootstrap", () =>
   Effect.gen(function* () {
     const tmp = yield* bootstrapFixture