mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 08:03:58 +00:00
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
58 lines
1.2 KiB
TypeScript
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()
|
|
})
|
|
}
|
|
}
|