UNPKG

@azure/event-hubs

Version:
278 lines (277 loc) • 9.39 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 batchingPartitionChannel_exports = {}; __export(batchingPartitionChannel_exports, { BatchingPartitionChannel: () => BatchingPartitionChannel }); module.exports = __toCommonJS(batchingPartitionChannel_exports); var import_core_amqp = require("@azure/core-amqp"); var import_core_util = require("@azure/core-util"); var import_awaitableQueue = require("./impl/awaitableQueue.js"); var import_getPromiseParts = require("./util/getPromiseParts.js"); var import_logger = require("./logger.js"); var import_core_util2 = require("@azure/core-util"); class BatchingPartitionChannel { _eventQueue = new import_awaitableQueue.AwaitableQueue(); _batchedEvents = []; _bufferCount = 0; _readyQueue = []; _flushState = { isFlushing: false }; _isRunning = false; _lastBatchCreationTime = 0; _loopAbortSignal; _maxBufferSize; _maxWaitTimeInMs; _onSendEventsErrorHandler; _onSendEventsSuccessHandler; _partitionId; _producer; constructor({ loopAbortSignal, maxBufferSize, maxWaitTimeInMs, onSendEventsErrorHandler, onSendEventsSuccessHandler, partitionId, producer }) { this._loopAbortSignal = loopAbortSignal; this._maxBufferSize = maxBufferSize; this._maxWaitTimeInMs = maxWaitTimeInMs; this._onSendEventsErrorHandler = onSendEventsErrorHandler; this._onSendEventsSuccessHandler = onSendEventsSuccessHandler; this._partitionId = partitionId; this._producer = producer; } getCurrentBufferedCount() { return this._bufferCount; } async enqueueEvent(event) { await this._ready(); this._eventQueue.push(event); this._bufferCount++; if (!this._isRunning) { this._isRunning = true; this._startPublishLoop().catch((e) => { import_logger.logger.error( `The following error occurred during batch creation or sending: ${JSON.stringify( e, void 0, " " )}` ); }); } } /** * Sets the flush state so that no new events can be enqueued until * all the currently buffered events are sent to the Event Hub. * * Returns a promise that resolves once flushing is complete. */ async flush(_options = {}) { const state = this._flushState; if (state.isFlushing) { return state.currentPromise; } if (this.getCurrentBufferedCount() === 0) { return Promise.resolve(); } const { promise, resolve } = (0, import_getPromiseParts.getPromiseParts)(); this._flushState = { isFlushing: true, currentPromise: promise, resolve }; return promise; } /** * Returns a promise that resolves once there is room for events to be added * to the buffer. */ _ready() { const currentBufferedCount = this.getCurrentBufferedCount(); if (currentBufferedCount < this._maxBufferSize && !this._readyQueue.length && !this._flushState.isFlushing) { return Promise.resolve(); } const { promise: readyPromise, reject, resolve } = (0, import_getPromiseParts.getPromiseParts)(); this._readyQueue.push({ resolve, reject }); return readyPromise; } /** * Starts the loop that creates batches and sends them to the Event Hub. * * The loop will run until the `_loopAbortSignal` is aborted. */ async _startPublishLoop() { let batch; let eventToAddToBatch; while (!this._loopAbortSignal.aborted) { try { if (!(0, import_core_util.isDefined)(batch)) { batch = await this._createBatch(); } const timeSinceLastBatchCreation = Date.now() - this._lastBatchCreationTime; const maximumTimeToWaitForEvent = batch.count ? Math.max(this._maxWaitTimeInMs - timeSinceLastBatchCreation, 0) : this._maxWaitTimeInMs; const event = eventToAddToBatch ?? await (0, import_core_util2.cancelablePromiseRace)( [ (abortOptions) => this._eventQueue.shift(abortOptions), (abortOptions) => (0, import_core_amqp.delay)( maximumTimeToWaitForEvent, abortOptions.abortSignal, abortOptions.abortErrorMsg ) ], { abortSignal: this._loopAbortSignal } ); if (!event) { if (batch.count) { await this._producer.sendBatch(batch); this._reportSuccess(); batch = await this._createBatch(); } continue; } else if (!eventToAddToBatch) { eventToAddToBatch = event; } const didAdd = batch.tryAdd(event); if (didAdd) { this._batchedEvents.push(event); eventToAddToBatch = void 0; } if (didAdd && batch.count >= this._maxBufferSize) { await this._producer.sendBatch(batch); this._reportSuccess(); batch = await this._createBatch(); } else if (!didAdd && batch.count) { await this._producer.sendBatch(batch); this._reportSuccess(); batch = await this._createBatch(); } if (!didAdd && !batch.tryAdd(event)) { this._reportFailure(new Error("Placeholder for max message size exceeded"), event); } else if (!didAdd) { this._batchedEvents.push(event); } eventToAddToBatch = void 0; } catch (err) { if (!(0, import_core_util.isObjectWithProperties)(err, ["name"]) || err.name !== "AbortError") { this._reportFailure(err); batch = void 0; this._batchedEvents = []; } } } } /** * Helper method that returns an `EventDataBatch`. * This also has the side effects of * - keeping track of batch creation time: needed for maxWaitTime calculations. * - clearing reference to batched events. * - incrementing the readiness: creating a new batch indicates the buffer * should have room, so we can resolve some pending `ready()` calls. */ async _createBatch() { this._lastBatchCreationTime = Date.now(); this._batchedEvents = []; const batch = await this._producer.createBatch({ partitionId: this._partitionId }); this._incrementReadiness(); return batch; } /** * This method will resolve as many pending `ready()` calls as it can * based on how much space remains in the buffer. * * If the channel is currently flushing, this is a no-op. This prevents * `enqueueEvent` calls from adding the event to the buffer until flushing * completes. */ _incrementReadiness() { if (this._flushState.isFlushing) { return; } const currentBufferedCount = this.getCurrentBufferedCount(); const num = Math.min(this._maxBufferSize - currentBufferedCount, this._readyQueue.length); for (let i = 0; i < num; i++) { this._readyQueue.shift()?.resolve(); } } /** * Calls the user-provided `onSendEventsSuccessHandler` with the events * that were successfully sent. */ _reportSuccess() { this._bufferCount = this._bufferCount - this._batchedEvents.length; this._updateFlushState(); try { this._onSendEventsSuccessHandler?.({ events: this._batchedEvents, partitionId: this._partitionId }); } catch (e) { import_logger.logger.error( `The following error occurred in the onSendEventsSuccessHandler: ${JSON.stringify( e, void 0, " " )}` ); } } /** * Calls the user-provided `onSendEventsErrorHandler` with an error and the events * that were not successfully sent. */ _reportFailure(err, event) { this._bufferCount = this._bufferCount - (event ? 1 : this._batchedEvents.length); this._updateFlushState(); try { this._onSendEventsErrorHandler({ error: err, events: event ? [event] : this._batchedEvents, partitionId: this._partitionId }); } catch (e) { import_logger.logger.error( `The following error occurred in the onSendEventsErrorHandler: ${JSON.stringify( e, void 0, " " )}` ); } } /** * Updates the channel's flush state once the size of the * event buffer has decreased to 0. */ _updateFlushState() { const state = this._flushState; if (!state.isFlushing || this.getCurrentBufferedCount() !== 0) { return; } state.resolve(); this._flushState = { isFlushing: false }; this._incrementReadiness(); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BatchingPartitionChannel }); //# sourceMappingURL=batchingPartitionChannel.js.map