Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
103 lines
2.7 KiB
TypeScript
103 lines
2.7 KiB
TypeScript
export type PluginWorkerSlotLease = {
|
|
release(): void
|
|
}
|
|
|
|
type SlotWaiter = {
|
|
signal: AbortSignal
|
|
resolve: (lease: PluginWorkerSlotLease) => void
|
|
reject: (error: Error) => void
|
|
onAbort: () => void
|
|
}
|
|
|
|
function cancellationError(): Error {
|
|
return new Error('plugin worker activation was cancelled')
|
|
}
|
|
|
|
/** Atomically leases the bounded worker slots and hands releases to queued
|
|
* activations in FIFO order. */
|
|
export class PluginWorkerSlotPool {
|
|
private readonly waiters: SlotWaiter[] = []
|
|
private leased = 0
|
|
private disposed = false
|
|
|
|
constructor(private readonly capacity: number) {
|
|
if (!Number.isInteger(capacity) || capacity <= 0) {
|
|
throw new Error('plugin worker capacity must be a positive integer')
|
|
}
|
|
}
|
|
|
|
acquire(signal: AbortSignal): Promise<PluginWorkerSlotLease> {
|
|
if (this.disposed) {
|
|
return Promise.reject(new Error('plugin worker slots are shut down'))
|
|
}
|
|
if (signal.aborted) {
|
|
return Promise.reject(cancellationError())
|
|
}
|
|
if (this.leased < this.capacity && this.waiters.length === 0) {
|
|
this.leased += 1
|
|
return Promise.resolve(this.createLease())
|
|
}
|
|
return new Promise<PluginWorkerSlotLease>((resolve, reject) => {
|
|
let cancelled = false
|
|
const waiter: SlotWaiter = {
|
|
signal,
|
|
resolve,
|
|
reject,
|
|
onAbort: () => {
|
|
if (cancelled) {
|
|
return
|
|
}
|
|
cancelled = true
|
|
const index = this.waiters.indexOf(waiter)
|
|
if (index !== -1) {
|
|
this.waiters.splice(index, 1)
|
|
}
|
|
signal.removeEventListener('abort', waiter.onAbort)
|
|
reject(cancellationError())
|
|
this.drain()
|
|
}
|
|
}
|
|
this.waiters.push(waiter)
|
|
signal.addEventListener('abort', waiter.onAbort, { once: true })
|
|
})
|
|
}
|
|
|
|
dispose(): void {
|
|
if (this.disposed) {
|
|
return
|
|
}
|
|
this.disposed = true
|
|
for (const waiter of this.waiters.splice(0)) {
|
|
waiter.signal.removeEventListener('abort', waiter.onAbort)
|
|
waiter.reject(new Error('plugin worker slots are shut down'))
|
|
}
|
|
}
|
|
|
|
private createLease(): PluginWorkerSlotLease {
|
|
let released = false
|
|
return {
|
|
release: () => {
|
|
if (released) {
|
|
return
|
|
}
|
|
released = true
|
|
this.leased -= 1
|
|
this.drain()
|
|
}
|
|
}
|
|
}
|
|
|
|
private drain(): void {
|
|
while (!this.disposed && this.leased < this.capacity && this.waiters.length > 0) {
|
|
const waiter = this.waiters.shift()!
|
|
waiter.signal.removeEventListener('abort', waiter.onAbort)
|
|
if (waiter.signal.aborted) {
|
|
waiter.reject(cancellationError())
|
|
continue
|
|
}
|
|
this.leased += 1
|
|
waiter.resolve(this.createLease())
|
|
}
|
|
}
|
|
}
|