HTTPCLIENT-2318 - Enhance PoolingHttpClientConnectionManager with isShutdown State Check.

This commit introduces an `isShutdown` method to the `PoolingHttpClientConnectionManager` class, providing a thread-safe way to check whether the connection manager has been closed. The addition leverages an existing `AtomicBoolean` closed flag to ensure that the shutdown state can be queried reliably in concurrent environments.
This commit is contained in:
Arturo Bernal 2024-02-20 20:39:10 +01:00 committed by Oleg Kalnichevski
parent 8a7f707a61
commit 407b9152cc
4 changed files with 91 additions and 2 deletions

View File

@ -711,8 +711,9 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan
*
* @return {@code true} if the connection manager has been shut down and is closed, otherwise
* return {@code false}.
* @since 5.4
*/
boolean isClosed() {
public boolean isClosed() {
return this.closed.get();
}

View File

@ -423,6 +423,11 @@ public class PoolingHttpClientConnectionManager
if (LOG.isDebugEnabled()) {
LOG.debug("{} releasing endpoint", ConnPoolSupport.getId(endpoint));
}
if (this.isClosed()) {
return;
}
final ManagedHttpClientConnection conn = entry.getConnection();
if (conn != null && keepAlive == null) {
conn.close(CloseMode.GRACEFUL);
@ -522,11 +527,17 @@ public class PoolingHttpClientConnectionManager
if (LOG.isDebugEnabled()) {
LOG.debug("Closing connections idle longer than {}", idleTime);
}
if (isClosed()) {
return;
}
this.pool.closeIdle(idleTime);
}
@Override
public void closeExpired() {
if (isClosed()) {
return;
}
LOG.debug("Closing expired connections");
this.pool.closeExpired();
}
@ -796,4 +807,17 @@ public class PoolingHttpClientConnectionManager
}
/**
* Method that can be called to determine whether the connection manager has been shut down and
* is closed or not.
*
* @return {@code true} if the connection manager has been shut down and is closed, otherwise
* return {@code false}.
* @since 5.4
*/
public boolean isClosed() {
return this.closed.get();
}
}

View File

@ -394,6 +394,9 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
if (LOG.isDebugEnabled()) {
LOG.debug("{} releasing endpoint", ConnPoolSupport.getId(endpoint));
}
if (this.isClosed()) {
return;
}
final ManagedAsyncClientConnection connection = entry.getConnection();
boolean reusable = connection != null && connection.isOpen();
try {
@ -575,11 +578,17 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
@Override
public void closeIdle(final TimeValue idletime) {
if (isClosed()) {
return;
}
pool.closeIdle(idletime);
}
@Override
public void closeExpired() {
if (isClosed()) {
return;
}
pool.closeExpired();
}
@ -773,8 +782,9 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
*
* @return {@code true} if the connection manager has been shut down and is closed, otherwise
* return {@code false}.
* @since 5.4
*/
boolean isClosed() {
public boolean isClosed() {
return this.closed.get();
}

View File

@ -30,6 +30,8 @@ package org.apache.hc.client5.http.impl.io;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -351,4 +353,56 @@ public class TestPoolingHttpClientConnectionManager {
socket, "somehost", 443, tlsConfig, context);
}
@Test
public void testIsShutdownInitially() {
Assertions.assertFalse(mgr.isClosed(), "Connection manager should not be shutdown initially.");
}
@Test
public void testShutdownIdempotency() {
mgr.close();
Assertions.assertTrue(mgr.isClosed(), "Connection manager should remain shutdown after the first call to shutdown.");
mgr.close(); // Second call to shutdown
Assertions.assertTrue(mgr.isClosed(), "Connection manager should still be shutdown after subsequent calls to shutdown.");
}
@Test
public void testLeaseAfterShutdown() {
mgr.close();
Assertions.assertThrows(IllegalArgumentException.class, () -> {
// Attempt to lease a connection after shutdown
mgr.lease("some-id", new HttpRoute(new HttpHost("localhost")), null);
}, "Attempting to lease a connection after shutdown should throw an exception.");
}
@Test
public void testIsShutdown() {
// Setup phase
Mockito.when(pool.isShutdown()).thenReturn(false, true); // Simulate changing states
// Execution phase: Initially, the manager should not be shutdown
Assertions.assertFalse(mgr.isClosed(), "Connection manager should not be shutdown initially.");
// Simulate shutting down the manager
mgr.close();
// Verification phase: Now, the manager should be reported as shutdown
Assertions.assertTrue(mgr.isClosed(), "Connection manager should be shutdown after close() is called.");
}
@Test
public void testConcurrentShutdown() throws InterruptedException {
final ExecutorService executor = Executors.newFixedThreadPool(2);
// Submit two shutdown tasks to be run in parallel, explicitly calling close() with no arguments
executor.submit(() -> mgr.close());
executor.submit(() -> mgr.close());
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
Assertions.assertTrue(mgr.isClosed(), "Connection manager should be shutdown after concurrent calls to shutdown.");
}
}