@ydbjs/topic
Version:
YDB Topics client for publish-subscribe messaging. Provides at-least-once delivery, exactly-once publishing, FIFO guarantees, and scalable message processing for unstructured data.
139 lines • 4.75 kB
JavaScript
import { loggers } from '@ydbjs/debug';
let dbg = loggers.topic.extend('queue');
export class AsyncPriorityQueue {
paused = false;
closed = false;
heap = [];
pendingShift;
pendingResume;
get size() {
return this.heap.length;
}
push(value, priority = 0) {
if (this.closed) {
dbg.log('push rejected, queue closed');
throw new Error('Queue closed');
}
dbg.log('pushing item %o with priority %d, current size: %d', value, priority, this.heap.length);
let left = 0;
let right = this.heap.length;
while (left < right) {
let mid = (left + right) >> 1;
if (this.heap[mid].priority < priority) {
right = mid;
}
else {
left = mid + 1;
}
}
this.heap.splice(left, 0, { value, priority });
if (this.pendingShift && this.heap.length > 0) {
dbg.log('resolving pending shift operation with item %o', this.heap[0]);
const next = this.heap.shift();
const resolve = this.pendingShift;
delete this.pendingShift;
resolve({ value: next.value, done: false });
}
dbg.log('item pushed, new size: %d', this.heap.length);
}
async next() {
if (this.paused) {
dbg.log('queue paused, waiting for resume');
await new Promise((resolve) => {
this.pendingResume = resolve;
});
dbg.log('queue resumed');
}
// Return done if closed and no items to process
if (this.closed && this.heap.length === 0) {
dbg.log('queue closed and empty, returning done');
return { value: undefined, done: true };
}
if (this.heap.length > 0) {
let next = this.heap.shift();
dbg.log('returning item %o with priority %d, remaining size: %d', next.value, next.priority, this.heap.length);
return { value: next.value, done: false };
}
// If we reach here: not closed and no items in heap
// Create pending operation to wait for new items
dbg.log('queue empty, creating pending shift operation');
return new Promise((resolve) => {
this.pendingShift = resolve;
});
}
pause() {
dbg.log('pausing queue');
this.paused = true;
}
resume() {
if (!this.paused) {
return;
}
dbg.log('resuming queue');
this.paused = false;
if (this.pendingResume) {
const resolve = this.pendingResume;
delete this.pendingResume;
resolve();
}
}
close() {
dbg.log('closing queue with %d pending items', this.heap.length);
this.closed = true;
// Resolve any pending operations with done: true
if (this.pendingShift) {
dbg.log('resolving pending shift with done: true');
let resolve = this.pendingShift;
delete this.pendingShift;
resolve({ value: undefined, done: true });
}
if (this.pendingResume) {
dbg.log('resolving pending resume');
let resolve = this.pendingResume;
delete this.pendingResume;
resolve();
}
}
reset() {
dbg.log('resetting queue, clearing %d items', this.heap.length);
this.paused = false;
this.closed = false;
this.heap.length = 0;
// Resolve any pending operations with done: true
if (this.pendingShift) {
dbg.log('resolving pending shift with done: true');
let resolve = this.pendingShift;
delete this.pendingShift;
resolve({ value: undefined, done: true });
}
if (this.pendingResume) {
dbg.log('resolving pending resume');
let resolve = this.pendingResume;
delete this.pendingResume;
resolve();
}
}
dispose() {
dbg.log('disposing queue, clearing %d items', this.heap.length);
// Clear the heap to prevent memory leaks
this.heap.length = 0;
// Close and resolve pending operations
this.close();
}
async *[Symbol.asyncIterator]() {
dbg.log('starting async iteration');
while (true) {
// eslint-disable-next-line no-await-in-loop
let { value, done } = await this.next();
if (done) {
dbg.log('async iteration completed');
break;
}
yield value;
}
}
[Symbol.dispose]() {
this.dispose();
}
}
//# sourceMappingURL=queue.js.map