@azure/cosmos
Version:
Microsoft Azure Cosmos DB Service Node.js SDK for NOSQL API
216 lines (215 loc) • 6.92 kB
JavaScript
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
});