diff --git a/packages/opencode/src/util/lock.ts b/packages/opencode/src/util/lock.ts deleted file mode 100644 index 15635996ee..0000000000 --- a/packages/opencode/src/util/lock.ts +++ /dev/null @@ -1,98 +0,0 @@ -const locks = new Map< - string, - { - readers: number - writer: boolean - waitingReaders: (() => void)[] - waitingWriters: (() => void)[] - } ->() - -function get(key: string) { - if (!locks.has(key)) { - locks.set(key, { - readers: 0, - writer: false, - waitingReaders: [], - waitingWriters: [], - }) - } - return locks.get(key)! -} - -function process(key: string) { - const lock = locks.get(key) - if (!lock || lock.writer || lock.readers > 0) return - - // Prioritize writers to prevent starvation - if (lock.waitingWriters.length > 0) { - const nextWriter = lock.waitingWriters.shift()! - nextWriter() - return - } - - // Wake up all waiting readers - while (lock.waitingReaders.length > 0) { - const nextReader = lock.waitingReaders.shift()! - nextReader() - } - - // Clean up empty locks - if (lock.readers === 0 && !lock.writer && lock.waitingReaders.length === 0 && lock.waitingWriters.length === 0) { - locks.delete(key) - } -} - -export async function read(key: string): Promise { - const lock = get(key) - - return new Promise((resolve) => { - if (!lock.writer && lock.waitingWriters.length === 0) { - lock.readers++ - resolve({ - [Symbol.dispose]: () => { - lock.readers-- - process(key) - }, - }) - } else { - lock.waitingReaders.push(() => { - lock.readers++ - resolve({ - [Symbol.dispose]: () => { - lock.readers-- - process(key) - }, - }) - }) - } - }) -} - -export async function write(key: string): Promise { - const lock = get(key) - - return new Promise((resolve) => { - if (!lock.writer && lock.readers === 0) { - lock.writer = true - resolve({ - [Symbol.dispose]: () => { - lock.writer = false - process(key) - }, - }) - } else { - lock.waitingWriters.push(() => { - lock.writer = true - resolve({ - [Symbol.dispose]: () => { - lock.writer = false - process(key) - }, - }) - }) - } - }) -} - -export * as Lock from "./lock" diff --git a/packages/opencode/test/util/lock.test.ts b/packages/opencode/test/util/lock.test.ts deleted file mode 100644 index 79fbb58316..0000000000 --- a/packages/opencode/test/util/lock.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { Lock } from "@/util/lock" - -function tick() { - return new Promise((r) => queueMicrotask(r)) -} - -async function flush(n = 5) { - for (let i = 0; i < n; i++) await tick() -} - -describe("util.lock", () => { - test("writer exclusivity: blocks reads and other writes while held", async () => { - const key = "lock:" + Math.random().toString(36).slice(2) - - const state = { - writer2: false, - reader: false, - writers: 0, - } - - // Acquire writer1 - using writer1 = await Lock.write(key) - state.writers++ - expect(state.writers).toBe(1) - - // Start writer2 candidate (should block) - const writer2Task = (async () => { - const w = await Lock.write(key) - state.writers++ - expect(state.writers).toBe(1) - state.writer2 = true - // Hold for a tick so reader cannot slip in - await tick() - return w - })() - - // Start reader candidate (should block) - const readerTask = (async () => { - const r = await Lock.read(key) - state.reader = true - return r - })() - - // Flush microtasks and assert neither acquired - await flush() - expect(state.writer2).toBe(false) - expect(state.reader).toBe(false) - - // Release writer1 - writer1[Symbol.dispose]() - state.writers-- - - // writer2 should acquire next - const writer2 = await writer2Task - expect(state.writer2).toBe(true) - - // Reader still blocked while writer2 held - await flush() - expect(state.reader).toBe(false) - - // Release writer2 - writer2[Symbol.dispose]() - state.writers-- - - // Reader should now acquire - const reader = await readerTask - expect(state.reader).toBe(true) - - reader[Symbol.dispose]() - }) -})