Skip to content

Commit 57cd99f

Browse files
authored
[ServiceBus] throw error earlier when timing out (Azure#27308)
Related: issue #9775, Azure#27010 This PR fixed the following timing issue when trying to establish link for next session but there are none available. what's happening is - SDK is trying to create a link for next avaiable session (`options.source.filter: { com.microsoft:session-filter': undefined }`) - Because no sessions are available, after one minute, a link is established, but with `"source":null,"target":null`. The call to create link resolves. - However, the service immediately use that link to ask SDK to close the link, with an error of `"com.microsoft:timeout"`/`The operation did not complete within the allotted timeout of 00:00:59.9130000.` - SDK respects the request and start acquiring a lock to close the link - Meanwhile, SDK gets a link, nice! It will continue as the link has been created - SDK gets lock to close link and sets `this._link` to `undefined` and start closing receiver and amqp session - SDK null-check the link and throws INTERNAL ERROR Instead of returning the link in this case, This PR checks whether we got a session id back. If not and there's an error from service, we rethrow the error. Some of the existing error handling is also moved before returning the link, instead of afterwards. ### Are there test cases added in this PR? _(If not, why?)_ There's one test covers this scenario and verifies two possible errors. https://github.com/Azure/azure-sdk-for-js/blob/28cbcd053daabb7f86816014d8cb8f8004bbc18f/sdk/servicebus/service-bus/test/public/sessionsTests.spec.ts#L81
1 parent 912ed6f commit 57cd99f

File tree

3 files changed

+50
-29
lines changed

3 files changed

+50
-29
lines changed

sdk/servicebus/service-bus/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bugs Fixed
1010

11+
- Fix an INTERNAL ERROR due to timing [PR #27308](https://github.com/Azure/azure-sdk-for-js/pull/27308)
12+
1113
### Other Changes
1214

1315
## 7.9.1 (2023-09-12)

sdk/servicebus/service-bus/src/serviceBusError.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export const wellKnownMessageCodesToServiceBusCodes: Map<string, ServiceBusError
8383
["ServerBusyError", "ServiceBusy"],
8484

8585
["OperationTimeoutError", "ServiceTimeout"],
86+
["ServiceUnavailableError", "ServiceTimeout"],
8687
["ServiceCommunicationError", "ServiceCommunicationProblem"],
8788
["SessionCannotBeLockedError", "SessionCannotBeLocked"],
8889
["SessionLockLostError", "SessionLockLost"],

sdk/servicebus/service-bus/src/session/messageSession.ts

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
import { OperationOptionsBase } from "../modelsToBeSharedWithEventHubs";
3838
import { ServiceBusError, translateServiceBusError } from "../serviceBusError";
3939
import { abandonMessage, completeMessage } from "../receivers/receiverCommon";
40-
import { isDefined } from "@azure/core-util";
40+
import { delay, isDefined } from "@azure/core-util";
4141

4242
/**
4343
* Describes the options that need to be provided while creating a message session receiver link.
@@ -257,11 +257,50 @@ export class MessageSession extends LinkEntity<Receiver> {
257257
}
258258
}
259259

260-
protected createRheaLink(
260+
protected async createRheaLink(
261261
options: ReceiverOptions,
262262
_abortSignal?: AbortSignalLike
263263
): Promise<Receiver> {
264-
return this._context.connection.createReceiver(options);
264+
this._lastSBError = undefined;
265+
let errorMessage: string = "";
266+
267+
const link = await this._context.connection.createReceiver(options);
268+
269+
const receivedSessionId = link.source?.filter?.[Constants.sessionFilterName];
270+
if (!this._providedSessionId && !receivedSessionId) {
271+
// When we ask for any sessions (passing option of session-filter: undefined),
272+
// but don't receive one back, check whether service has sent any error.
273+
if (
274+
options.source &&
275+
typeof options.source !== "string" &&
276+
options.source.filter &&
277+
Constants.sessionFilterName in options.source.filter &&
278+
options.source.filter![Constants.sessionFilterName] === undefined
279+
) {
280+
await delay(1); // yield to eventloop
281+
if (this._lastSBError) {
282+
throw this._lastSBError;
283+
}
284+
}
285+
// Ideally this code path should never be reached as `MessageSession.createReceiver()` should fail instead
286+
// TODO: https://github.com/Azure/azure-sdk-for-js/issues/9775 to figure out why this code path indeed gets hit.
287+
errorMessage = `Failed to create a receiver. No unlocked sessions available.`;
288+
} else if (this._providedSessionId && receivedSessionId !== this._providedSessionId) {
289+
// This code path is reached if the session is already locked by another receiver.
290+
// TODO: Check why the service would not throw an error or just timeout instead of giving a misleading successful receiver
291+
errorMessage = `Failed to create a receiver for the requested session '${this._providedSessionId}'. It may be locked by another receiver.`;
292+
}
293+
294+
if (errorMessage) {
295+
const error = translateServiceBusError({
296+
description: errorMessage,
297+
condition: ErrorNameConditionMapper.SessionCannotBeLockedError,
298+
});
299+
logger.logError(error, this.logPrefix);
300+
throw error;
301+
}
302+
303+
return link;
265304
}
266305

267306
/**
@@ -274,36 +313,13 @@ export class MessageSession extends LinkEntity<Receiver> {
274313
const sessionOptions = this._createMessageSessionOptions(this.identifier, opts.timeoutInMs);
275314
await this.initLink(sessionOptions, opts.abortSignal);
276315

277-
if (this.link == null) {
316+
if (!this.link) {
278317
throw new Error("INTERNAL ERROR: failed to create receiver but without an error.");
279318
}
280319

281-
const receivedSessionId =
282-
this.link.source &&
283-
this.link.source.filter &&
284-
this.link.source.filter[Constants.sessionFilterName];
285-
286-
let errorMessage: string = "";
287-
288-
if (this._providedSessionId == null && receivedSessionId == null) {
289-
// Ideally this code path should never be reached as `MessageSession.createReceiver()` should fail instead
290-
// TODO: https://github.com/Azure/azure-sdk-for-js/issues/9775 to figure out why this code path indeed gets hit.
291-
errorMessage = `Failed to create a receiver. No unlocked sessions available.`;
292-
} else if (this._providedSessionId != null && receivedSessionId !== this._providedSessionId) {
293-
// This code path is reached if the session is already locked by another receiver.
294-
// TODO: Check why the service would not throw an error or just timeout instead of giving a misleading successful receiver
295-
errorMessage = `Failed to create a receiver for the requested session '${this._providedSessionId}'. It may be locked by another receiver.`;
296-
}
320+
const receivedSessionId = this.link.source?.filter?.[Constants.sessionFilterName];
297321

298-
if (errorMessage) {
299-
const error = translateServiceBusError({
300-
description: errorMessage,
301-
condition: ErrorNameConditionMapper.SessionCannotBeLockedError,
302-
});
303-
logger.logError(error, this.logPrefix);
304-
throw error;
305-
}
306-
if (this._providedSessionId == null) this.sessionId = receivedSessionId;
322+
if (!this._providedSessionId) this.sessionId = receivedSessionId;
307323
this.sessionLockedUntilUtc = convertTicksToDate(
308324
this.link.properties["com.microsoft:locked-until-utc"]
309325
);
@@ -371,6 +387,7 @@ export class MessageSession extends LinkEntity<Receiver> {
371387
}
372388

373389
private _retryOptions: RetryOptions | undefined;
390+
private _lastSBError: Error | ServiceBusError | undefined;
374391

375392
/**
376393
* Constructs a MessageSession instance which lets you receive messages as batches
@@ -444,6 +461,7 @@ export class MessageSession extends LinkEntity<Receiver> {
444461
if (sbError.code === "SessionLockLostError") {
445462
sbError.message = `The session lock has expired on the session with id ${this.sessionId}.`;
446463
}
464+
this._lastSBError = sbError;
447465
logger.logError(sbError, "%s An error occurred for Receiver", this.logPrefix);
448466
this._notifyError({
449467
error: sbError,

0 commit comments

Comments
 (0)