This is an automated email from the ASF dual-hosted git repository.

tabish121 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-protonj2.git


The following commit(s) were added to refs/heads/main by this push:
     new 9988d2e4 PROTON-2996 Handle remote close for default sender and session
9988d2e4 is described below

commit 9988d2e46c116634c62d2cfa891cfcbcbb8f683c
Author: Timothy Bish <[email protected]>
AuthorDate: Mon Nov 2 15:33:07 2026 -0500

    PROTON-2996 Handle remote close for default sender and session
    
    When the Connection default sender or session is remotely closed the
    connection should reset the state such that a future call to use the
    default sender or session recreates them and returns the new instance
---
 .../protonj2/client/impl/ClientConnection.java     |  13 ++
 .../qpid/protonj2/client/impl/ClientLinkType.java  |  12 +-
 .../qpid/protonj2/client/impl/ClientSession.java   |  13 ++
 .../qpid/protonj2/client/impl/ConnectionTest.java  |  86 ++++++++++++
 .../qpid/protonj2/client/impl/SenderTest.java      | 149 +++++++++++++++++++++
 5 files changed, 267 insertions(+), 6 deletions(-)

diff --git 
a/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientConnection.java
 
b/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientConnection.java
index 03cc362f..6afd394e 100644
--- 
a/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientConnection.java
+++ 
b/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientConnection.java
@@ -932,6 +932,19 @@ public final class ClientConnection implements Connection {
     private ClientSession lazyCreateConnectionSession() throws ClientException 
{
         if (connectionSession == null) {
             connectionSession = sessionBuilder.session(null).open();
+            connectionSession.remotelyClosedHandler((session) -> {
+                try {
+                    session.closeAsync();
+                } catch (Throwable ignore) {}
+
+                // Clear the old default session and sender, a lazy create 
needs to construct a new sender.
+                connectionSession = null;
+
+                if (connectionSender != null) {
+                    connectionSender.closeAsync();
+                    connectionSender = null;
+                }
+            });
         }
 
         return connectionSession;
diff --git 
a/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientLinkType.java
 
b/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientLinkType.java
index b2155942..13bbd998 100644
--- 
a/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientLinkType.java
+++ 
b/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientLinkType.java
@@ -355,17 +355,17 @@ public abstract class ClientLinkType<LinkType extends 
Link<LinkType>,
         linkSpecificRemoteCloseHandler();
 
         if (link.isLocallyOpen()) {
-            if (linkRemotelyClosedHandler != null) {
-                try {
-                    linkRemotelyClosedHandler.accept(self());
-                } catch (Throwable ignore) {}
-            }
-
             
immediateLinkShutdown(ClientExceptionSupport.convertToLinkClosedException(
                 link.getRemoteCondition(), "Link remotely closed without 
explanation from the remote"));
         } else {
             immediateLinkShutdown(failureCause);
         }
+
+        if (linkRemotelyClosedHandler != null) {
+            try {
+                linkRemotelyClosedHandler.accept(self());
+            } catch (Throwable ignore) {}
+        }
     }
 
     protected final void handleParentEndpointClosed(ProtonType link) {
diff --git 
a/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientSession.java
 
b/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientSession.java
index 0b812597..3ffb3f14 100644
--- 
a/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientSession.java
+++ 
b/protonj2-client/src/main/java/org/apache/qpid/protonj2/client/impl/ClientSession.java
@@ -23,6 +23,7 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 import org.apache.qpid.protonj2.client.ErrorCondition;
@@ -75,6 +76,7 @@ public class ClientSession implements Session {
     private volatile int closed;
     private volatile ClientException failureCause;
     private ClientTransactionContext txnContext = NO_OP_TXN_CONTEXT;
+    protected Consumer<ClientSession> sessionRemotelyClosedHandler;
 
     private org.apache.qpid.protonj2.engine.Session protonSession;
 
@@ -424,6 +426,11 @@ public class ClientSession implements Session {
 
     //----- Internal API accessible for use within the package
 
+    final ClientSession remotelyClosedHandler(Consumer<ClientSession> handler) 
{
+        this.sessionRemotelyClosedHandler = handler;
+        return this;
+    }
+
     ClientSession open() {
         protonSession.localOpenHandler(this::handleLocalOpen)
                      .localCloseHandler(this::handleLocalClose)
@@ -579,6 +586,12 @@ public class ClientSession implements Session {
         } else {
             immediateSessionShutdown(failureCause);
         }
+
+        if (sessionRemotelyClosedHandler != null) {
+            try {
+                sessionRemotelyClosedHandler.accept(this);
+            } catch (Throwable ignore) {}
+        }
     }
 
     private void handleEngineShutdown(Engine engine) {
diff --git 
a/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/ConnectionTest.java
 
b/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/ConnectionTest.java
index b2bea64a..03c695c6 100644
--- 
a/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/ConnectionTest.java
+++ 
b/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/ConnectionTest.java
@@ -19,6 +19,7 @@ package org.apache.qpid.protonj2.client.impl;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -1960,6 +1961,91 @@ public class ConnectionTest extends 
ImperativeClientTestCase {
         }
     }
 
+    @Test
+    public void testDefaultSessionRecreatedOnNextUseIfRemotelyClosed() throws 
Exception {
+        try (ProtonTestServer peer = new 
ProtonTestServer(testServerOptions())) {
+            peer.expectSASLAnonymousConnect();
+            peer.expectOpen().respond();
+            peer.expectBegin().respond();
+            peer.start();
+
+            URI remoteURI = peer.getServerURI();
+
+            LOG.info("Connect test started, peer listening on: {}", remoteURI);
+
+            Client container = Client.create();
+            Connection connection = container.connect(remoteURI.getHost(), 
remoteURI.getPort(), connectionOptions());
+            connection.openFuture().get(10, TimeUnit.SECONDS);
+
+            final Session defaultSession1 = connection.defaultSession();
+            assertNotNull(defaultSession1);
+            defaultSession1.openFuture().get(10, TimeUnit.SECONDS);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectEnd();
+            peer.remoteEnd().now();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectBegin().respond();
+
+            final Session defaultSession2 = connection.defaultSession();
+            assertNotNull(defaultSession2);
+            defaultSession2.openFuture().get(10, TimeUnit.SECONDS);
+
+            assertNotSame(defaultSession1, defaultSession2);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectClose().respond();
+
+            connection.close();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+        }
+    }
+
+    @Test
+    public void 
testUserClosesDefaultSessionResultsInNewSessionWhenRemotelyClosed() throws 
Exception {
+        try (ProtonTestServer peer = new 
ProtonTestServer(testServerOptions())) {
+            peer.expectSASLAnonymousConnect();
+            peer.expectOpen().respond();
+            peer.expectBegin().respond();
+            peer.start();
+
+            URI remoteURI = peer.getServerURI();
+
+            LOG.info("Connect test started, peer listening on: {}", remoteURI);
+
+            Client container = Client.create();
+            Connection connection = container.connect(remoteURI.getHost(), 
remoteURI.getPort(), connectionOptions());
+            connection.openFuture().get(10, TimeUnit.SECONDS);
+
+            final Session defaultSession1 = connection.defaultSession();
+            assertNotNull(defaultSession1);
+            defaultSession1.openFuture().get(10, TimeUnit.SECONDS);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectEnd().respond();
+
+            defaultSession1.close();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectBegin().respond();
+
+            final Session defaultSession2 = connection.defaultSession();
+            assertNotNull(defaultSession2);
+            defaultSession2.openFuture().get(10, TimeUnit.SECONDS);
+
+            assertNotSame(defaultSession1, defaultSession2);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectClose().respond();
+
+            connection.close();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+        }
+    }
+
     @Disabled("Disabled due to requirement of hard coded port")
     @Test
     public void testLocalPortOption() throws Exception {
diff --git 
a/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/SenderTest.java
 
b/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/SenderTest.java
index 7d6f7d36..9386c7f8 100644
--- 
a/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/SenderTest.java
+++ 
b/protonj2-client/src/test/java/org/apache/qpid/protonj2/client/impl/SenderTest.java
@@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -3033,4 +3034,152 @@ public class SenderTest extends 
ImperativeClientTestCase {
             peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
         }
     }
+
+    @Test
+    public void 
testDefaultSenderRecreatedOnNextUseIfDefaultSessionRemotelyClosed() throws 
Exception {
+        try (ProtonTestServer peer = new ProtonTestServer()) {
+            peer.expectSASLAnonymousConnect();
+            
peer.expectOpen().withDesiredCapabilities(ClientConstants.ANONYMOUS_RELAY.toString())
+                             .respond()
+                             
.withOfferedCapabilities(ClientConstants.ANONYMOUS_RELAY.toString());
+            peer.expectBegin().respond();
+            peer.expectAttach().ofSender().respond();
+            peer.start();
+
+            URI remoteURI = peer.getServerURI();
+
+            LOG.info("Connect test started, peer listening on: {}", remoteURI);
+
+            Client container = Client.create();
+            Connection connection = container.connect(remoteURI.getHost(), 
remoteURI.getPort());
+            connection.openFuture().get(10, TimeUnit.SECONDS);
+
+            final Sender defaultSender1 = connection.defaultSender();
+
+            assertNotNull(defaultSender1);
+
+            defaultSender1.openFuture().get(10, TimeUnit.SECONDS);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectEnd();
+            peer.remoteEnd().now();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectBegin().respond();
+            peer.expectAttach().ofSender().respond();
+
+            final Sender defaultSender2 = connection.defaultSender();
+            assertNotNull(defaultSender2);
+            defaultSender2.openFuture().get(10, TimeUnit.SECONDS);
+
+            assertNotNull(defaultSender1);
+            assertNotSame(defaultSender1, defaultSender2);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectClose().respond();
+
+            connection.close();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+        }
+    }
+
+    @Test
+    public void testDefaultSenderRecreatedWhenUserClosesDefaultSession() 
throws Exception {
+        try (ProtonTestServer peer = new ProtonTestServer()) {
+            peer.expectSASLAnonymousConnect();
+            
peer.expectOpen().withDesiredCapabilities(ClientConstants.ANONYMOUS_RELAY.toString())
+                             .respond()
+                             
.withOfferedCapabilities(ClientConstants.ANONYMOUS_RELAY.toString());
+            peer.expectBegin().respond();
+            peer.expectAttach().ofSender().respond();
+            peer.start();
+
+            URI remoteURI = peer.getServerURI();
+
+            LOG.info("Connect test started, peer listening on: {}", remoteURI);
+
+            Client container = Client.create();
+            Connection connection = container.connect(remoteURI.getHost(), 
remoteURI.getPort());
+            connection.openFuture().get(10, TimeUnit.SECONDS);
+
+            final Sender defaultSender1 = connection.defaultSender();
+
+            assertNotNull(defaultSender1);
+
+            defaultSender1.openFuture().get(10, TimeUnit.SECONDS);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectEnd().respond();
+
+            connection.defaultSession().close();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectBegin().respond();
+            peer.expectAttach().ofSender().respond();
+
+            final Sender defaultSender2 = connection.defaultSender();
+            assertNotNull(defaultSender2);
+            defaultSender2.openFuture().get(10, TimeUnit.SECONDS);
+
+            assertNotNull(defaultSender1);
+            assertNotSame(defaultSender1, defaultSender2);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectClose().respond();
+
+            connection.close();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+        }
+    }
+
+    @Test
+    public void 
testUserClosesDefaultSenderResultsInNewSenderWhenRemotelyClosed() throws 
Exception {
+        try (ProtonTestServer peer = new ProtonTestServer()) {
+            peer.expectSASLAnonymousConnect();
+            
peer.expectOpen().withDesiredCapabilities(ClientConstants.ANONYMOUS_RELAY.toString())
+                             .respond()
+                             
.withOfferedCapabilities(ClientConstants.ANONYMOUS_RELAY.toString());
+            peer.expectBegin().respond();
+            peer.expectAttach().ofSender().respond();
+            peer.start();
+
+            URI remoteURI = peer.getServerURI();
+
+            LOG.info("Connect test started, peer listening on: {}", remoteURI);
+
+            Client container = Client.create();
+            Connection connection = container.connect(remoteURI.getHost(), 
remoteURI.getPort());
+            connection.openFuture().get(10, TimeUnit.SECONDS);
+
+            final Sender defaultSender1 = connection.defaultSender();
+
+            assertNotNull(defaultSender1);
+
+            defaultSender1.openFuture().get(10, TimeUnit.SECONDS);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectDetach().respond();
+
+            defaultSender1.close();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectAttach().ofSender().respond();
+
+            final Sender defaultSender2 = connection.defaultSender();
+            assertNotNull(defaultSender2);
+            defaultSender2.openFuture().get(10, TimeUnit.SECONDS);
+
+            assertNotNull(defaultSender1);
+            assertNotSame(defaultSender1, defaultSender2);
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+            peer.expectClose().respond();
+
+            connection.close();
+
+            peer.waitForScriptToComplete(5, TimeUnit.SECONDS);
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to