chore: delete unused util/lock module (#27223)
This commit is contained in:
@@ -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<Disposable> {
|
|
||||||
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<Disposable> {
|
|
||||||
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"
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import { Lock } from "@/util/lock"
|
|
||||||
|
|
||||||
function tick() {
|
|
||||||
return new Promise<void>((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]()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user