refactor(instance): remove state helper (#22381)
This commit is contained in:
@@ -6,7 +6,6 @@ import { Log } from "@/util/log"
|
|||||||
import { LocalContext } from "../util/local-context"
|
import { LocalContext } from "../util/local-context"
|
||||||
import { Project } from "./project"
|
import { Project } from "./project"
|
||||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||||
import { State } from "./state"
|
|
||||||
|
|
||||||
export interface InstanceContext {
|
export interface InstanceContext {
|
||||||
directory: string
|
directory: string
|
||||||
@@ -113,13 +112,10 @@ export const Instance = {
|
|||||||
restore<R>(ctx: InstanceContext, fn: () => R): R {
|
restore<R>(ctx: InstanceContext, fn: () => R): R {
|
||||||
return context.provide(ctx, fn)
|
return context.provide(ctx, fn)
|
||||||
},
|
},
|
||||||
state<S>(init: () => S, dispose?: (state: Awaited<S>) => Promise<void>): () => S {
|
|
||||||
return State.create(() => Instance.directory, init, dispose)
|
|
||||||
},
|
|
||||||
async reload(input: { directory: string; init?: () => Promise<any>; project?: Project.Info; worktree?: string }) {
|
async reload(input: { directory: string; init?: () => Promise<any>; project?: Project.Info; worktree?: string }) {
|
||||||
const directory = Filesystem.resolve(input.directory)
|
const directory = Filesystem.resolve(input.directory)
|
||||||
Log.Default.info("reloading instance", { directory })
|
Log.Default.info("reloading instance", { directory })
|
||||||
await Promise.all([State.dispose(directory), disposeInstance(directory)])
|
await disposeInstance(directory)
|
||||||
cache.delete(directory)
|
cache.delete(directory)
|
||||||
const next = track(directory, boot({ ...input, directory }))
|
const next = track(directory, boot({ ...input, directory }))
|
||||||
|
|
||||||
@@ -141,7 +137,7 @@ export const Instance = {
|
|||||||
const directory = Instance.directory
|
const directory = Instance.directory
|
||||||
const project = Instance.project
|
const project = Instance.project
|
||||||
Log.Default.info("disposing instance", { directory })
|
Log.Default.info("disposing instance", { directory })
|
||||||
await Promise.all([State.dispose(directory), disposeInstance(directory)])
|
await disposeInstance(directory)
|
||||||
cache.delete(directory)
|
cache.delete(directory)
|
||||||
|
|
||||||
GlobalBus.emit("event", {
|
GlobalBus.emit("event", {
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import { Log } from "@/util/log"
|
|
||||||
|
|
||||||
export namespace State {
|
|
||||||
interface Entry {
|
|
||||||
state: any
|
|
||||||
dispose?: (state: any) => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
const log = Log.create({ service: "state" })
|
|
||||||
const recordsByKey = new Map<string, Map<any, Entry>>()
|
|
||||||
|
|
||||||
export function create<S>(root: () => string, init: () => S, dispose?: (state: Awaited<S>) => Promise<void>) {
|
|
||||||
return () => {
|
|
||||||
const key = root()
|
|
||||||
let entries = recordsByKey.get(key)
|
|
||||||
if (!entries) {
|
|
||||||
entries = new Map<string, Entry>()
|
|
||||||
recordsByKey.set(key, entries)
|
|
||||||
}
|
|
||||||
const exists = entries.get(init)
|
|
||||||
if (exists) return exists.state as S
|
|
||||||
const state = init()
|
|
||||||
entries.set(init, {
|
|
||||||
state,
|
|
||||||
dispose,
|
|
||||||
})
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function dispose(key: string) {
|
|
||||||
const entries = recordsByKey.get(key)
|
|
||||||
if (!entries) return
|
|
||||||
|
|
||||||
log.info("waiting for state disposal to complete", { key })
|
|
||||||
|
|
||||||
let disposalFinished = false
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!disposalFinished) {
|
|
||||||
log.warn(
|
|
||||||
"state disposal is taking an unusually long time - if it does not complete in a reasonable time, please report this as a bug",
|
|
||||||
{ key },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, 10000).unref()
|
|
||||||
|
|
||||||
const tasks: Promise<void>[] = []
|
|
||||||
for (const [init, entry] of entries) {
|
|
||||||
if (!entry.dispose) continue
|
|
||||||
|
|
||||||
const label = typeof init === "function" ? init.name : String(init)
|
|
||||||
|
|
||||||
const task = Promise.resolve(entry.state)
|
|
||||||
.then((state) => entry.dispose!(state))
|
|
||||||
.catch((error) => {
|
|
||||||
log.error("Error while disposing state:", { error, key, init: label })
|
|
||||||
})
|
|
||||||
|
|
||||||
tasks.push(task)
|
|
||||||
}
|
|
||||||
await Promise.all(tasks)
|
|
||||||
|
|
||||||
entries.clear()
|
|
||||||
recordsByKey.delete(key)
|
|
||||||
|
|
||||||
disposalFinished = true
|
|
||||||
log.info("state disposal completed", { key })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import { afterEach, expect, test } from "bun:test"
|
|
||||||
|
|
||||||
import { Instance } from "../../src/project/instance"
|
|
||||||
import { tmpdir } from "../fixture/fixture"
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await Instance.disposeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Instance.state caches values for the same instance", async () => {
|
|
||||||
await using tmp = await tmpdir()
|
|
||||||
let n = 0
|
|
||||||
const state = Instance.state(() => ({ n: ++n }))
|
|
||||||
|
|
||||||
await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => {
|
|
||||||
const a = state()
|
|
||||||
const b = state()
|
|
||||||
expect(a).toBe(b)
|
|
||||||
expect(n).toBe(1)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Instance.state isolates values by directory", async () => {
|
|
||||||
await using a = await tmpdir()
|
|
||||||
await using b = await tmpdir()
|
|
||||||
let n = 0
|
|
||||||
const state = Instance.state(() => ({ n: ++n }))
|
|
||||||
|
|
||||||
const x = await Instance.provide({
|
|
||||||
directory: a.path,
|
|
||||||
fn: async () => state(),
|
|
||||||
})
|
|
||||||
const y = await Instance.provide({
|
|
||||||
directory: b.path,
|
|
||||||
fn: async () => state(),
|
|
||||||
})
|
|
||||||
const z = await Instance.provide({
|
|
||||||
directory: a.path,
|
|
||||||
fn: async () => state(),
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(x).toBe(z)
|
|
||||||
expect(x).not.toBe(y)
|
|
||||||
expect(n).toBe(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Instance.state is disposed on instance reload", async () => {
|
|
||||||
await using tmp = await tmpdir()
|
|
||||||
const seen: string[] = []
|
|
||||||
let n = 0
|
|
||||||
const state = Instance.state(
|
|
||||||
() => ({ n: ++n }),
|
|
||||||
async (value) => {
|
|
||||||
seen.push(String(value.n))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const a = await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => state(),
|
|
||||||
})
|
|
||||||
await Instance.reload({ directory: tmp.path })
|
|
||||||
const b = await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => state(),
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(a).not.toBe(b)
|
|
||||||
expect(seen).toEqual(["1"])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Instance.state is disposed on disposeAll", async () => {
|
|
||||||
await using a = await tmpdir()
|
|
||||||
await using b = await tmpdir()
|
|
||||||
const seen: string[] = []
|
|
||||||
const state = Instance.state(
|
|
||||||
() => ({ dir: Instance.directory }),
|
|
||||||
async (value) => {
|
|
||||||
seen.push(value.dir)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
await Instance.provide({
|
|
||||||
directory: a.path,
|
|
||||||
fn: async () => state(),
|
|
||||||
})
|
|
||||||
await Instance.provide({
|
|
||||||
directory: b.path,
|
|
||||||
fn: async () => state(),
|
|
||||||
})
|
|
||||||
await Instance.disposeAll()
|
|
||||||
|
|
||||||
expect(seen.sort()).toEqual([a.path, b.path].sort())
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Instance.state dedupes concurrent promise initialization", async () => {
|
|
||||||
await using tmp = await tmpdir()
|
|
||||||
let n = 0
|
|
||||||
const state = Instance.state(async () => {
|
|
||||||
n += 1
|
|
||||||
await Bun.sleep(10)
|
|
||||||
return { n }
|
|
||||||
})
|
|
||||||
|
|
||||||
const [a, b] = await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => Promise.all([state(), state()]),
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(a).toBe(b)
|
|
||||||
expect(n).toBe(1)
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user