@azure/service-bus
Version:
Azure Service Bus SDK for JavaScript
118 lines • 4.63 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { AbortError } from "@azure/abort-controller";
import { ReceiverEvents } from "rhea-promise";
import { receiverLogger as logger } from "../log";
import { ServiceBusError } from "../serviceBusError";
import { receiveDrainTimeoutInMs } from "../util/constants";
/**
* Wraps the receiver with some higher level operations for managing state
* like credits, draining, etc...
*
* @internal
*/
export class ReceiverHelper {
constructor(_getCurrentReceiver) {
this._getCurrentReceiver = _getCurrentReceiver;
this._isSuspended = true;
}
_getCurrentReceiverOrError() {
const currentReceiverData = this._getCurrentReceiver();
if (currentReceiverData.receiver == null) {
return "is undefined";
}
if (!currentReceiverData.receiver.isOpen()) {
return "is not open";
}
if (this._isSuspended) {
return "is suspended";
}
return currentReceiverData;
}
/**
* Adds credits to the receiver, respecting any state that
* indicates the receiver is closed or should not continue
* to receive more messages.
*
* @param credits - Number of credits to add.
* or `stopReceivingMessages` has been called.
*/
addCredit(credits) {
const currentReceiverOrError = this._getCurrentReceiverOrError();
if (typeof currentReceiverOrError === "string") {
const errorMessage = `Cannot request messages on the receiver since it ${currentReceiverOrError}.`;
if (currentReceiverOrError === "is suspended") {
// if a user has suspended the receiver we should consider this a non-retryable
// error since it absolutely requires user intervention.
throw new AbortError(errorMessage);
}
throw new ServiceBusError(errorMessage, "GeneralError");
}
if (currentReceiverOrError.receiver != null) {
logger.verbose(`${currentReceiverOrError.logPrefix} Adding ${credits} credits`);
currentReceiverOrError.receiver.addCredit(credits);
}
}
/**
* Drains the credits for the receiver and prevents the `receiverHelper.addCredit()` method from adding credits.
* Call `resume()` to enable the `addCredit()` method.
*/
async suspend() {
const { receiver, logPrefix } = this._getCurrentReceiver();
this._isSuspended = true;
if (!this._isValidReceiver(receiver)) {
return;
}
logger.verbose(`${logPrefix} User has requested to stop receiving new messages, attempting to drain.`);
return this.drain();
}
/**
* Resets tracking so `addCredit` works again by toggling the `_isSuspended` flag.
*/
resume() {
this._isSuspended = false;
}
isSuspended() {
return this._isSuspended;
}
/**
* Initiates a drain for the current receiver and resolves when
* the drain has completed.
*
* NOTE: This method returns immediately if the receiver is not valid or if there
* are no pending credits on the receiver (ie: `receiver.credit === 0`).
*/
async drain() {
const { receiver, logPrefix } = this._getCurrentReceiver();
if (!this._isValidReceiver(receiver)) {
// TODO: should we throw?
return;
}
if (receiver.credit === 0) {
// nothing to drain
return;
}
logger.verbose(`${logPrefix} Receiver is starting drain. Remaining credits; ${receiver.credit}`);
const drainPromise = new Promise((resolve) => {
const timer = setTimeout(async () => {
logger.warning(`${logPrefix} Time out when draining credits in suspend().`);
// Close the receiver link since we have not received the receiver_drained event
// to prevent out-of-sync link state between local and remote
await receiver?.close();
resolve();
}, receiveDrainTimeoutInMs);
receiver.once(ReceiverEvents.receiverDrained, () => {
logger.verbose(`${logPrefix} Receiver has been drained.`);
receiver.drain = false;
clearTimeout(timer);
resolve();
});
receiver.drainCredit();
});
return drainPromise;
}
_isValidReceiver(receiver) {
return receiver != null && receiver.isOpen();
}
}
//# sourceMappingURL=receiverHelper.js.map