mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-30 03:54:59 +00:00
Same nested-dispose-in-onCleanup bug as 7f36ac2481 but in three more places: TerminalProvider.disposeAll, PromptProvider.disposeAll, and scoped-cache.clear() (covers viewCache.clear and comments cache.clear). All of them synchronously call createRoot dispose() on cached entries inside onCleanup, which during a server switch nests into the outer cleanNode cascade and throws TypeError at chunk-*.js:992. Snapshot the pending disposers, clear the cache synchronously, and fire the disposers on a microtask so the outer cleanup finishes first.
72 lines
2 KiB
TypeScript
72 lines
2 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import { createScopedCache } from "./scoped-cache"
|
|
|
|
describe("createScopedCache", () => {
|
|
test("evicts least-recently-used entry when max is reached", () => {
|
|
const disposed: string[] = []
|
|
const cache = createScopedCache((key) => ({ key }), {
|
|
maxEntries: 2,
|
|
dispose: (value) => disposed.push(value.key),
|
|
})
|
|
|
|
const a = cache.get("a")
|
|
const b = cache.get("b")
|
|
expect(a.key).toBe("a")
|
|
expect(b.key).toBe("b")
|
|
|
|
cache.get("a")
|
|
const c = cache.get("c")
|
|
|
|
expect(c.key).toBe("c")
|
|
expect(cache.peek("a")?.key).toBe("a")
|
|
expect(cache.peek("b")).toBeUndefined()
|
|
expect(cache.peek("c")?.key).toBe("c")
|
|
expect(disposed).toEqual(["b"])
|
|
})
|
|
|
|
test("disposes entries on delete and clear", async () => {
|
|
const disposed: string[] = []
|
|
const cache = createScopedCache((key) => ({ key }), {
|
|
dispose: (value) => disposed.push(value.key),
|
|
})
|
|
|
|
cache.get("a")
|
|
cache.get("b")
|
|
|
|
const removed = cache.delete("a")
|
|
expect(removed?.key).toBe("a")
|
|
expect(cache.peek("a")).toBeUndefined()
|
|
|
|
cache.clear()
|
|
expect(cache.peek("b")).toBeUndefined()
|
|
// clear() defers dispose to a microtask to avoid nested cleanNode cascades
|
|
// when called from inside an onCleanup; flush the queue before asserting.
|
|
await Promise.resolve()
|
|
expect(disposed).toEqual(["a", "b"])
|
|
})
|
|
|
|
test("expires stale entries with ttl and recreates on get", () => {
|
|
let clock = 0
|
|
let count = 0
|
|
const disposed: string[] = []
|
|
const cache = createScopedCache((key) => ({ key, count: ++count }), {
|
|
ttlMs: 10,
|
|
now: () => clock,
|
|
dispose: (value) => disposed.push(`${value.key}:${value.count}`),
|
|
})
|
|
|
|
const first = cache.get("a")
|
|
expect(first.count).toBe(1)
|
|
|
|
clock = 9
|
|
expect(cache.peek("a")?.count).toBe(1)
|
|
|
|
clock = 11
|
|
expect(cache.peek("a")).toBeUndefined()
|
|
expect(disposed).toEqual(["a:1"])
|
|
|
|
const second = cache.get("a")
|
|
expect(second.count).toBe(2)
|
|
expect(disposed).toEqual(["a:1"])
|
|
})
|
|
})
|