-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[service-bus] Make close() work when a connection is in process #8746
[service-bus] Make close() work when a connection is in process #8746
Conversation
… close a connection if it's in progress.
…process wait for it to complete so we can properly close down the connection.
Can we pull this PR out of the draft stage? |
…onReceiver is now.
@ramya-rao-a - I found some issues and I think I want to look at it with fresh eyes tomorrow morning:
As it is my tests are failing but I think the ideas make sense. Just need to take a longer look at it. |
… - the new rules are: - If you create a receiver it should be created and set before you do any async calls. This ensures that if the user calls close() it actually routes to the instance that is getting "initialized" so all calls are properly accounted for. - All the locking logic remains in the lower level classes (messageSesssion or messageReceiver). The only things that change in the higher level Receiver/SessionReceiver classes is that they separate out the assignment of the internal field (this._context.<receiver> or this._messageSession) from it's initialization.
@ramya-rao-a - okay, it looks correct now. The basic pattern was:
|
/azp run js - servicebus - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
- If the result of the _init() for the streaming receiver is a useless receiver (ie, one that isn't open) we can just remove it from context immediately rather than keep it around. - Don't overwrite the context.streamingReceiver. It's okay to call _init() multiple times on the same instance. This is just a redundant check since there is an "already receiving" check above it but...it's nice to have a simple check in there as well. - If _init() is called on a closed MessageReceiver throw the standard "this receiver is closed" non-retryable error - it'll never be valid.
…clear the context.streamingReceiver value if it just comes back invalid" commit.
/azp run js - servicebus - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
@@ -146,6 +153,10 @@ export class Receiver { | |||
return; | |||
}) | |||
.catch((err) => { | |||
if (this._context.streamingReceiver != null && !this._context.streamingReceiver.isOpen()) { | |||
this._context.streamingReceiver = undefined; | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What scenario does this cover?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the "init() failed and we have a streaming receiver that is not open/doesn't own any resources".
There was a test covering this (Streaming - Failed init should not cache recevier
) that revealed this behavior which seems sensible. If a streaming receiver is just completely dead (ie, nothing to clean up) it's safe to just not cache it at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this change is because we are now caching first, initializing later?
A cached streaming receiver symbolizes a receiver that needs to be recovered when there is a connection issue. I am slightly concerned on the implications of this change in order on link recovery.
Take the case of init() failing and a connection issue happening at around the same time.
If the connection recovery parts of the code get executed before the catch block here, then this streaming receiver would be on its merry way to being recovered leading to something like #5541
Can we refactor so that we keep the init first, cache later as before, but still have the changes you want?
const sReceiver = StreamingReceiver.create(..);
sReceiver.init().then(() => {
if (this.isClosed) {
await sReceiver.close();
return;
};
this._context.streamingReceiver = sReceiver;
sReceiver.receive(...)
}).catch (err) {
onError(err);
}
@@ -174,7 +185,7 @@ export class Receiver { | |||
this._throwIfReceiverOrConnectionClosed(); | |||
this._throwIfAlreadyReceiving(); | |||
|
|||
if (!this._context.batchingReceiver || !this._context.batchingReceiver.isOpen()) { | |||
if (!this._context.batchingReceiver) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is driving this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at it it seemed unnecessary - we can call _init() multiple times on a batchingreceiver and each time will work the same (or early exit if it's already open).
This just made it consistent with the check I was doing for streamingReceiver.
(I can bring it back - I have no strong feelings on it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On thinking about this more, I think that the way I have it now is just simpler to reason through. We don't have to worry about any overlap/concurrency issues for the field anymore. It's either set or not and any other calls that manipulate it's state are protected by the lock.
If we don't do that then I have to start reasoning about whether it's possible for two concurrent instances of _context.batchingReceiver can be there (ie, we've swapped out an older one for a newer one and we're somehow initializing the older one). So I think this is worth keeping.
Co-authored-by: Ramya Rao <ramya.rao.a@outlook.com>
Co-authored-by: Harsha Nalluru <sanallur@microsoft.com>
…chardpark-msft/azure-sdk-for-js into richardpark-7986-proper-close
sdk/servicebus/service-bus/test/streamingReceiverSessions.spec.ts
Outdated
Show resolved
Hide resolved
…lock) looks like when we're in init()
Co-authored-by: Harsha Nalluru <sanallur@microsoft.com>
…r the truth (and the fact that it was throwing an error because I was connecting to the wrong queue) - Fixed the .catch's to either eliminate them entirely _or_ to only allow one specific error to avoid this situation in the future.
/azp run js - servicebus - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
return; | ||
} | ||
|
||
this.isConnecting = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isConnecting
property gets checked in multiple places to make the decision of whether to call onDetached()
or not so that there are not multiple attempts being made at recovering the link.
With the change in this PR, isConnecting
is now being set to true only after the lock is acquired, resulting in potential multiple calls to onDetached
now being a possibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But multiple calls to onDetached()
will add to the noise in the logs
So, I would recommend moving the this.isConnecting = true
to before the lock is acquired
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a deeper look at this. Your comments have me realize I need to consider the onDetached/open/close in tandem and I don't think I've done that enough.
…ageReceiver as a no-op.
…y, that's an explicit step for the caller). (this is still all internal to the library)
There are certain parts of this PR that are straight-forward and can be pulled out into a separate PR while we keep this PR open to think more on the ramifications of the changes being done. I recommend creating a separate PR for the below 2 changes
|
… richardpark-7986-proper-close
Closing this PR for now:
|
Currently it's possible for a user to call close() while a connection is in process. When this happens you can end up in a bad state where the object thinks it's closed but it still has open resources.
This commit changes it so:
Fixes #7986