Skip to content

Instantly share code, notes, and snippets.

@danneu
Created March 24, 2023 21:04
Show Gist options
  • Save danneu/ce4b6d99e170435fa8cdc61c17233ad1 to your computer and use it in GitHub Desktop.
Save danneu/ce4b6d99e170435fa8cdc61c17233ad1 to your computer and use it in GitHub Desktop.

Revisions

  1. danneu created this gist Mar 24, 2023.
    69 changes: 69 additions & 0 deletions queue.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,69 @@
    // USAGE

    const queue = new Queue(50)
    const middleware = () => async (ctx, next: () => Promise<void>) => {
    const lock = await queue.take()
    await next().finally(() => {
    queue.release(lock)
    })
    }

    // IMPL

    class MyLock {}
    class Ticket {
    resolve: (_: MyLock) => void
    constructor(resolve: (_: MyLock) => void) {
    this.resolve = resolve
    }
    }

    export default class Queue {
    inflightCapacity: number
    locks: Set<MyLock>
    pending: Array<Ticket>
    pendingCapacity: number

    constructor(inflightCapacity: number) {
    this.inflightCapacity = inflightCapacity
    this.locks = new Set()
    this.pending = []
    this.pendingCapacity = 1000
    }

    // When you take(), you get a promise that resolves into a lock once a lock
    // is available.
    async take(): Promise<MyLock> {
    if (this.locks.size < this.inflightCapacity) {
    // There are locks available
    const lock = new MyLock()
    this.locks.add(lock)
    return lock
    } else if (this.pending.length >= this.pendingCapacity) {
    // Drop request
    throw new Error('OVER_CAPACITY')
    } else {
    // Pend until a lock is available
    const promise = new Promise((resolve: (_: MyLock) => void) => {
    const ticket = new Ticket(resolve)
    this.pending.push(ticket)
    })
    return promise
    }
    }

    release(lock: MyLock) {
    this.locks.delete(lock)
    for (let i = 0; i < this.inflightCapacity - this.locks.size; i++) {
    // const ticket = this.pending.pop()
    const ticket = this.pending.shift()
    if (!ticket) {
    return
    }
    const lock = new MyLock()
    this.locks.add(lock)
    ticket.resolve(lock)
    }
    }
    }