refactor: migrate Effect call sites from Flock to EffectFlock (#22688)

This commit is contained in:
Kit Langton
2026-04-16 02:39:59 +00:00
committed by GitHub
parent 48f88af9aa
commit 6c7e9f6f3a
4 changed files with 463 additions and 472 deletions
+13 -26
View File
@@ -34,7 +34,8 @@ import type { ConsoleState } from "./console-state"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { InstanceState } from "@/effect/instance-state" import { InstanceState } from "@/effect/instance-state"
import { Context, Duration, Effect, Exit, Fiber, Layer, Option } from "effect" import { Context, Duration, Effect, Exit, Fiber, Layer, Option } from "effect"
import { Flock } from "@opencode-ai/shared/util/flock" import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared" import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared"
import { Npm } from "../npm" import { Npm } from "../npm"
import { InstanceRef } from "@/effect/instance-ref" import { InstanceRef } from "@/effect/instance-ref"
@@ -1144,14 +1145,18 @@ export const ConfigDirectoryTypoError = NamedError.create(
}), }),
) )
export const layer: Layer.Layer<Service, never, AppFileSystem.Service | Auth.Service | Account.Service | Env.Service> = export const layer: Layer.Layer<
Layer.effect( Service,
never,
AppFileSystem.Service | Auth.Service | Account.Service | Env.Service | EffectFlock.Service
> = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const fs = yield* AppFileSystem.Service const fs = yield* AppFileSystem.Service
const authSvc = yield* Auth.Service const authSvc = yield* Auth.Service
const accountSvc = yield* Account.Service const accountSvc = yield* Account.Service
const env = yield* Env.Service const env = yield* Env.Service
const flock = yield* EffectFlock.Service
const readConfigFile = Effect.fnUntraced(function* (filepath: string) { const readConfigFile = Effect.fnUntraced(function* (filepath: string) {
return yield* fs.readFileString(filepath).pipe( return yield* fs.readFileString(filepath).pipe(
@@ -1292,10 +1297,7 @@ export const layer: Layer.Layer<Service, never, AppFileSystem.Service | Auth.Ser
yield* Effect.promise(() => Npm.install(dir)) yield* Effect.promise(() => Npm.install(dir))
}) })
const installDependencies = Effect.fn("Config.installDependencies")(function* ( const installDependencies = Effect.fn("Config.installDependencies")(function* (dir: string, input?: InstallInput) {
dir: string,
input?: InstallInput,
) {
if ( if (
!(yield* fs.access(dir, { writable: true }).pipe( !(yield* fs.access(dir, { writable: true }).pipe(
Effect.as(true), Effect.as(true),
@@ -1304,25 +1306,9 @@ export const layer: Layer.Layer<Service, never, AppFileSystem.Service | Auth.Ser
) )
return return
const key = const key = process.platform === "win32" ? "config-install:win32" : `config-install:${AppFileSystem.resolve(dir)}`
process.platform === "win32" ? "config-install:win32" : `config-install:${AppFileSystem.resolve(dir)}`
yield* Effect.acquireUseRelease( yield* flock.withLock(install(dir), key).pipe(Effect.orDie)
Effect.promise((signal) =>
Flock.acquire(key, {
signal,
onWait: (tick) =>
input?.waitTick?.({
dir,
attempt: tick.attempt,
delay: tick.delay,
waited: tick.waited,
}),
}),
),
() => install(dir),
(lease) => Effect.promise(() => lease.release()),
)
}) })
const loadInstanceState = Effect.fn("Config.loadInstanceState")(function* (ctx: InstanceContext) { const loadInstanceState = Effect.fn("Config.loadInstanceState")(function* (ctx: InstanceContext) {
@@ -1632,9 +1618,10 @@ export const layer: Layer.Layer<Service, never, AppFileSystem.Service | Auth.Ser
waitForDependencies, waitForDependencies,
}) })
}), }),
) )
export const defaultLayer = layer.pipe( export const defaultLayer = layer.pipe(
Layer.provide(EffectFlock.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer), Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer), Layer.provide(Env.defaultLayer),
Layer.provide(Auth.defaultLayer), Layer.provide(Auth.defaultLayer),
+14 -12
View File
@@ -2,6 +2,8 @@ import { test, expect, describe, mock, afterEach, beforeEach, spyOn } from "bun:
import { Deferred, Effect, Fiber, Layer, Option } from "effect" import { Deferred, Effect, Fiber, Layer, Option } from "effect"
import { NodeFileSystem, NodePath } from "@effect/platform-node" import { NodeFileSystem, NodePath } from "@effect/platform-node"
import { Config } from "../../src/config" import { Config } from "../../src/config"
import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Auth } from "../../src/auth" import { Auth } from "../../src/auth"
import { AccessToken, Account, AccountID, OrgID } from "../../src/account" import { AccessToken, Account, AccountID, OrgID } from "../../src/account"
@@ -34,7 +36,10 @@ const emptyAuth = Layer.mock(Auth.Service)({
all: () => Effect.succeed({}), all: () => Effect.succeed({}),
}) })
const testFlock = EffectFlock.defaultLayer
const layer = Config.layer.pipe( const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer), Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer), Layer.provide(Env.defaultLayer),
Layer.provide(emptyAuth), Layer.provide(emptyAuth),
@@ -333,6 +338,7 @@ test("resolves env templates in account config with account token", async () =>
}) })
const layer = Config.layer.pipe( const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer), Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer), Layer.provide(Env.defaultLayer),
Layer.provide(emptyAuth), Layer.provide(emptyAuth),
@@ -879,11 +885,7 @@ it.live("dedupes concurrent config dependency installs for the same dir", () =>
yield* Deferred.await(ready) yield* Deferred.await(ready)
let done = false let done = false
const second = yield* installDeps(dir, { const second = yield* installDeps(dir).pipe(
waitTick: () => {
Deferred.doneUnsafe(blocked, Effect.void)
},
}).pipe(
Effect.tap(() => Effect.tap(() =>
Effect.sync(() => { Effect.sync(() => {
done = true done = true
@@ -892,7 +894,8 @@ it.live("dedupes concurrent config dependency installs for the same dir", () =>
Effect.forkScoped, Effect.forkScoped,
) )
yield* Deferred.await(blocked) // Give the second fiber time to hit the lock retry loop
yield* Effect.sleep(500)
expect(done).toBe(false) expect(done).toBe(false)
yield* Deferred.succeed(hold, void 0) yield* Deferred.succeed(hold, void 0)
@@ -955,12 +958,9 @@ it.live("serializes config dependency installs across dirs", () =>
const first = yield* installDeps(a).pipe(Effect.forkScoped) const first = yield* installDeps(a).pipe(Effect.forkScoped)
yield* Deferred.await(ready) yield* Deferred.await(ready)
const second = yield* installDeps(b, { const second = yield* installDeps(b).pipe(Effect.forkScoped)
waitTick: () => { // Give the second fiber time to hit the lock retry loop
Deferred.doneUnsafe(blocked, Effect.void) yield* Effect.sleep(500)
},
}).pipe(Effect.forkScoped)
yield* Deferred.await(blocked)
expect(peak).toBe(1) expect(peak).toBe(1)
yield* Deferred.succeed(hold, void 0) yield* Deferred.succeed(hold, void 0)
@@ -1826,6 +1826,7 @@ test("project config overrides remote well-known config", async () => {
}) })
const layer = Config.layer.pipe( const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer), Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer), Layer.provide(Env.defaultLayer),
Layer.provide(fakeAuth), Layer.provide(fakeAuth),
@@ -1882,6 +1883,7 @@ test("wellknown URL with trailing slash is normalized", async () => {
}) })
const layer = Config.layer.pipe( const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer), Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer), Layer.provide(Env.defaultLayer),
Layer.provide(fakeAuth), Layer.provide(fakeAuth),
+5 -3
View File
@@ -5,7 +5,7 @@ import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect"
import { NodeFileSystem } from "@effect/platform-node" import { NodeFileSystem } from "@effect/platform-node"
import { AppFileSystem } from "./filesystem" import { AppFileSystem } from "./filesystem"
import { Global } from "./global" import { Global } from "./global"
import { Flock } from "./util/flock" import { EffectFlock } from "./util/effect-flock"
export namespace Npm { export namespace Npm {
export class InstallFailedError extends Schema.TaggedErrorClass<InstallFailedError>()("NpmInstallFailedError", { export class InstallFailedError extends Schema.TaggedErrorClass<InstallFailedError>()("NpmInstallFailedError", {
@@ -62,6 +62,7 @@ export namespace Npm {
const afs = yield* AppFileSystem.Service const afs = yield* AppFileSystem.Service
const global = yield* Global.Service const global = yield* Global.Service
const fs = yield* FileSystem.FileSystem const fs = yield* FileSystem.FileSystem
const flock = yield* EffectFlock.Service
const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg)) const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg))
const outdated = Effect.fn("Npm.outdated")(function* (pkg: string, cachedVersion: string) { const outdated = Effect.fn("Npm.outdated")(function* (pkg: string, cachedVersion: string) {
@@ -92,7 +93,7 @@ export namespace Npm {
const add = Effect.fn("Npm.add")(function* (pkg: string) { const add = Effect.fn("Npm.add")(function* (pkg: string) {
const dir = directory(pkg) const dir = directory(pkg)
yield* Flock.effect(`npm-install:${dir}`) yield* flock.acquire(`npm-install:${dir}`)
const arborist = new Arborist({ const arborist = new Arborist({
path: dir, path: dir,
@@ -133,7 +134,7 @@ export namespace Npm {
}, Effect.scoped) }, Effect.scoped)
const install = Effect.fn("Npm.install")(function* (dir: string) { const install = Effect.fn("Npm.install")(function* (dir: string) {
yield* Flock.effect(`npm-install:${dir}`) yield* flock.acquire(`npm-install:${dir}`)
const reify = Effect.fnUntraced(function* () { const reify = Effect.fnUntraced(function* () {
const arb = new Arborist({ const arb = new Arborist({
@@ -240,6 +241,7 @@ export namespace Npm {
) )
export const defaultLayer = layer.pipe( export const defaultLayer = layer.pipe(
Layer.provide(EffectFlock.layer),
Layer.provide(AppFileSystem.layer), Layer.provide(AppFileSystem.layer),
Layer.provide(Global.layer), Layer.provide(Global.layer),
Layer.provide(NodeFileSystem.layer), Layer.provide(NodeFileSystem.layer),
+1 -1
View File
@@ -274,5 +274,5 @@ export namespace EffectFlock {
}), }),
) )
export const live = layer.pipe(Layer.provide(AppFileSystem.defaultLayer)) export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Global.layer))
} }