UNPKG

@azure/cosmos

Version:
216 lines (215 loc) • 6.92 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var Limiter_exports = {}; __export(Limiter_exports, { LimiterQueue: () => LimiterQueue }); module.exports = __toCommonJS(Limiter_exports); var import_statusCodes = require("../common/statusCodes.js"); class ListNode { value; next = null; prev = null; constructor(value) { this.value = value; } } class DoublyLinkedList { head = null; tail = null; length = 0; push(value) { const node = new ListNode(value); if (!this.head) { this.head = this.tail = node; } else { this.tail.next = node; node.prev = this.tail; this.tail = node; } this.length++; } shift() { if (!this.head) return null; const value = this.head.value; this.head = this.head.next; if (this.head) { this.head.prev = null; } else { this.tail = null; } this.length--; return value; } clear() { this.head = null; this.tail = null; this.length = 0; } isEmpty() { return this.length === 0; } } function scheduleCallback(fn) { if (typeof process !== "undefined" && typeof process.nextTick === "function") { process.nextTick(fn); } else if (typeof setImmediate === "function") { setImmediate(fn); } else { Promise.resolve().then(fn); } } class LimiterQueue { // maximum number of tasks allowed to run concurrently concurrency; // number of tasks currently executing running = 0; // doubly linked list to store batchers and resolve/reject functions for dispatch tasks tasks = new DoublyLinkedList(); // boolean flag that indicates whether the queue has been permanently paused terminated = false; // value to resolve with when the queue is terminated terminatedValue; // indicates if the processing cycle has been scheduled via the asynchronous scheduler scheduled = false; // indicates whether the queue is currently in the process of dequeueing and executing tasks processing = false; // retry callback to retry all the queued operations in case of split/merge error retrier; // partiton metric for collecting metrics for the requests partitionMetric; // callback used to refresh the partition key range cache in case of split/merge error refreshPartitionKeyRangeCache; refreshPKRangeCachePromise = void 0; /** * Creates a new HighPerformanceQueue. */ constructor(concurrency, partitionMetric, retrier, refreshPartitionKeyRangeCache) { this.concurrency = concurrency; this.partitionMetric = partitionMetric; this.retrier = retrier; this.refreshPartitionKeyRangeCache = refreshPartitionKeyRangeCache; } /** * Enqueue a task and return a Promise that resolves or rejects when the task completes. * If the queue has been terminated via pauseAndClear, the promise resolves immediately with the terminated value. */ push(batcher) { if (this.terminated) { const ops = batcher.getOperations(); ops.forEach((op) => this.retrier(op, op.operationContext.diagnosticNode)); return Promise.resolve(this.terminatedValue); } return new Promise((resolve, reject) => { this.tasks.push({ batcher, resolve, reject }); this.scheduleProcess(); }); } /** * Permanently pauses processing and clears the queue. * All queued tasks and subsequent push() calls will immediately resolve with the provided custom value. */ async pauseAndClear(customValue, diagnosticNode) { this.terminated = true; this.terminatedValue = customValue; const operationsList = []; while (!this.tasks.isEmpty()) { const queueItem = this.tasks.shift(); if (!queueItem) break; if (customValue === import_statusCodes.StatusCodes.Gone) { const operations = queueItem.batcher.getOperations(); operationsList.push(...operations); } queueItem.resolve(customValue); } if (customValue === import_statusCodes.StatusCodes.Gone) { if (this.refreshPKRangeCachePromise) { await this.refreshPKRangeCachePromise; } else { this.refreshPKRangeCachePromise = this.refreshPartitionKeyRangeCache(diagnosticNode); await this.refreshPKRangeCachePromise; } for (const operation of operationsList) { await this.retrier(operation, operation.operationContext.diagnosticNode); } } } /** * Schedules the processing loop using the best available asynchronous scheduler. */ scheduleProcess() { if (this.scheduled || this.processing || this.terminated) return; this.scheduled = true; scheduleCallback(() => { this.scheduled = false; this.process(); }); } /** * Processes tasks up to the concurrency limit. */ process() { if (this.terminated) return; this.processing = true; try { while (!this.terminated && this.running < this.concurrency && !this.tasks.isEmpty()) { const queueItem = this.tasks.shift(); if (!queueItem) break; this.running++; let dispatchPromise; try { dispatchPromise = queueItem.batcher.dispatch(this.partitionMetric); } catch (err) { queueItem.reject(err); this.running--; continue; } void dispatchPromise.then((result) => { queueItem.resolve(result); }).catch((err) => { queueItem.reject(err); }).finally(() => { this.running--; if (!this.terminated && this.running < this.concurrency && !this.tasks.isEmpty()) { this.scheduleProcess(); } }); } } catch (err) { console.error("Unexpected error in task queue processing:", err); } finally { this.processing = false; } } /** * Dynamically updates the concurrency limit. */ setConcurrency(newConcurrency) { if (newConcurrency < 1) { throw new Error("Concurrency must be at least 1"); } this.concurrency = newConcurrency; if (!this.terminated && this.running < this.concurrency) { this.scheduleProcess(); } } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { LimiterQueue });