@azure/event-hubs
Version:
Azure Event Hubs SDK for JS.
278 lines (277 loc) • 9.39 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 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