Files
orca/src/shared/priority-semaphore.ts
T
Neil 3f58d5cf9a fix(daemon): bound cwd validation per UNC route (#14967)
Async cwd validation dedupes by exact path but had no concurrency bound, and a
dead UNC share answers `stat` in ~21s while holding one of libuv's 4 default fs
threads. Four distinct paths on one unreachable server therefore starved every
other async fs read in the daemon — including the cold-restore history replay
running alongside them — which moves the head-of-line stall #14848 removed from
the event loop into the thread pool.

Adds a per-route lane of 2, reusing PrioritySemaphore and matching the per-distro
lane in rate-limits/auth-filesystem-operation.ts. Keyed by the host that has to
answer (WSL distro, or the `\\server` prefix) so many dead subdirectories of one
share fold into one lane. Local-disk paths bypass the lane entirely: a global cap
would queue a healthy local spawn behind a dead share.

Moves PrioritySemaphore to src/shared. It has no imports, and reaching into
src/main/daemon from src/main/providers inverted the dependency direction that
already runs daemon -> providers.

Note this bounds pool occupancy, which cancellation cannot: an aborted `stat`
still holds its libuv thread until the OS returns.

STA-4543
2026-08-16 16:50:26 -07:00

58 lines
1.2 KiB
TypeScript

type Waiter = {
priority: number
resolve: (release: () => void) => void
}
export class PrioritySemaphore {
private available: number
private waiters: Waiter[] = []
constructor(concurrency: number) {
this.available = concurrency
}
acquire(priority: number): Promise<() => void> {
if (this.available > 0) {
this.available--
let released = false
return Promise.resolve(() => {
if (released) {
return
}
released = true
this.release()
})
}
return new Promise<() => void>((resolve) => {
this.waiters.push({ priority, resolve })
})
}
private release(): void {
if (this.waiters.length === 0) {
this.available++
return
}
// Find the highest-priority (lowest number) waiter.
// Among equal priorities, take the first (FIFO).
let bestIdx = 0
for (let i = 1; i < this.waiters.length; i++) {
if (this.waiters[i].priority < this.waiters[bestIdx].priority) {
bestIdx = i
}
}
const waiter = this.waiters.splice(bestIdx, 1)[0]
let released = false
waiter.resolve(() => {
if (released) {
return
}
released = true
this.release()
})
}
}