Skip to content
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

[BUG] ServiceBusSessionReceiverClient closes only after timeout #39565

Closed
pawel-wilczopolski opened this issue Apr 5, 2024 · 5 comments
Closed
Assignees
Labels
Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-team-attention This issue needs attention from Azure service team or SDK team question The issue doesn't require a change to the product in order to be resolved. Most issues start as that Service Bus

Comments

@pawel-wilczopolski
Copy link

Describe the bug
After invoking ServiceBusSessionReceiverClient close() method, the operation lasts for the tryTimeout period (1 minute by default) of AmqpRetryOptions.
The client should be closed immediately instead of waiting 1 minute.

Exception or Stack Trace
The following log is always visible after timeout (default 1 minute) is reached:
"Timed out waiting for RequestResponseChannel to complete closing. Manually closing."

To Reproduce

  1. Accept any session via ServiceBusSessionReceiverClient.
  2. Invoke 'close()' method on both ServiceBusSessionReceiverClient and ServiceBusReceiverClient.
  3. ServiceBusSessionReceiverClient will be closing 1 minute.

Code Snippet

   public Mono<Void> closeAsync(){
        final Mono<Void> closeOperationWithTimeout  = closeMono.asMono().timeout(retryOptions.getTryTimeout()).onErrorResume(TimeoutException.class, error -> {
            return Mono.fromRunnable(() -> {

                logger.info("Timed out waiting for RequestResponseChannel to complete closing. Manually closing.");

                onTerminalState("SendLinkHandler");
                onTerminalState("ReceiveLinkHandler");
            });
        }).subscribeOn(Schedulers.boundedElastic());

    if (isDisposed.getAndSet(true)) {
        logger.verbose("Channel already closed.");
        return closeOperationWithTimeout;
    }

    logger.verbose("Closing request/response channel.");

    return Mono.fromRunnable(() -> {
        try {
            // Schedule API calls on proton-j entities on the ReactorThread associated with the connection.
            provider.getReactorDispatcher().invoke(() -> {
                logger.verbose("Closing send link and receive link.");

                sendLink.close();
                receiveLink.close();
            });
        } catch (IOException | RejectedExecutionException e) {
            logger.info("Unable to schedule close work. Closing manually.");

            sendLink.close();
            receiveLink.close();
        }
    }).subscribeOn(Schedulers.boundedElastic()).then(closeOperationWithTimeout);

Expected behavior
The ServiceBusSessionReceiverClient should be closed immediately.

Setup (please complete the following information):

  • OS: Windows 11
  • IDE: IntelliJ IDEA
  • Library/Libraries: azure-messaging-servicebus:7.15.2
  • Java version: 11
@github-actions github-actions bot added Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-team-attention This issue needs attention from Azure service team or SDK team question The issue doesn't require a change to the product in order to be resolved. Most issues start as that Service Bus labels Apr 5, 2024
Copy link

github-actions bot commented Apr 5, 2024

@anuchandy @conniey @lmolkova

Copy link

github-actions bot commented Apr 5, 2024

Thank you for your feedback. Tagging and routing to the team member best able to assist.

@anuchandy
Copy link
Member

anuchandy commented Apr 10, 2024

Hello @pawel-wilczopolski hope you're doing good. @conniey and I discussed this today. So, the internal state machine of AMQP library is asynchronous and the reason for it wait on force close is to ensure any lingering messages, dispositions (complete|abandon) enqueued and other state in transit is processed, so things are cleaned up gracefully.

If a continuous receive and high throughput is desired, it is recommended to use the ServiceBusProcessorClient. Please see here. As it says there, the use of Synchronous Client (ServiceBus[Session|Receiver]Client) is typically discouraged, because the user has to deal with more errors and possible message expirations. On the other hand, the ServiceBusProcessorClient handles errors automatically and takes care of multi-threading, so receive from multiple sessions can happen in parallel. For the long term, ServiceBusProcessorClient will help the applications be more reliable.

If Synchronous client is still needed, and if you’re going to build a new ServiceBusSessionClient instance or restart the service after the close, you may do a fire-and-forget close, i.e, invoke close on a separate thread and giving it a few seconds before the restart/building new client using a new builder. The code may looks like-

// The ServiceBusSessionReceiver is no longer needed.
// Fire and forget the client close
try {
CompletableFuture.supplyAsync(() -> {
    // call ServiceBusSessionReceiver::close.
    return null;
})
.toCompletableFuture().get(5, TimeUnit.SECONDS);
} catch (Exception e) {
    // log(e);
}
// Service restarts or application creates new ServiceBusSessionReceiver

@pawel-wilczopolski
Copy link
Author

pawel-wilczopolski commented Apr 11, 2024 via email

@anuchandy
Copy link
Member

anuchandy commented May 28, 2024

Adding additional notes on the safety of the above workaround provided - when offloading the closure with a client-side timeout, there are two execution paths, both leading to eventual shutdown and garbage collection of reactor-executor thread, i.e., the reactor-executor thread won’t stay around and will not have any impact.

• [Execution-Path-1] Either application or host (VM, container) exit after calling close().
• [Execution-Path-2] Or, the application calls close() and continues with creating a new Client.

In the first case, since the JVM exits, all threads get cleaned up as part of the exit. In the second case, the old reactor-executor thread will continue its final cleanup activities on the background (without blocking the main thread) and exit.

We’ve created a work item to improve this experience. Linking it here, please track it there. Closing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-team-attention This issue needs attention from Azure service team or SDK team question The issue doesn't require a change to the product in order to be resolved. Most issues start as that Service Bus
Projects
None yet
Development

No branches or pull requests

2 participants