refactor: migrate Effect call sites from Flock to EffectFlock (#22688)
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
@@ -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,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),
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user