Skip to content

Commit 8aed624

Browse files
committed
HTTPCLIENT-2379: PerRoutePool.release now decrements the counter first and keeps the entry in the map until the count reaches zero, regardless of whether the connection is still open. The entry is handed back to the parent pool exactly once—on that final release—and any attempt to release an entry that was never leased now raises an IllegalStateException.
1 parent 6e0a18f commit 8aed624

3 files changed

Lines changed: 32 additions & 60 deletions

File tree

httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/H2SharingConnPool.java

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -270,16 +270,17 @@ long track(final PoolEntry<T, C> entry) {
270270
PoolEntry<T, C> lease() {
271271
lock.lock();
272272
try {
273-
final PoolEntry<T, C> entry = entryMap.entrySet().stream()
273+
return entryMap.entrySet().stream()
274+
.filter(e -> {
275+
final C conn = e.getKey().getConnection();
276+
return conn != null && conn.isOpen();
277+
})
274278
.min(Comparator.comparingLong(e -> e.getValue().get()))
275-
.map(Map.Entry::getKey)
279+
.map(e -> {
280+
e.getValue().incrementAndGet();
281+
return e.getKey();
282+
})
276283
.orElse(null);
277-
if (entry == null) {
278-
return null;
279-
}
280-
final AtomicLong counter = getCounter(entry);
281-
counter.incrementAndGet();
282-
return entry;
283284
} finally {
284285
lock.unlock();
285286
}
@@ -288,20 +289,18 @@ PoolEntry<T, C> lease() {
288289
long release(final PoolEntry<T, C> entry, final boolean reusable) {
289290
lock.lock();
290291
try {
291-
final C connection = entry.getConnection();
292-
if (!reusable || connection == null || !connection.isOpen()) {
293-
entryMap.remove(entry);
294-
return 0;
295-
} else {
296-
final AtomicLong counter = entryMap.compute(entry, (e, c) -> {
297-
if (c == null) {
298-
return null;
299-
}
300-
final long count = c.decrementAndGet();
301-
return count > 0 ? c : null;
302-
});
303-
return counter != null ? counter.get() : 0L;
292+
if (!reusable) {
293+
entry.discardConnection(CloseMode.GRACEFUL);
304294
}
295+
296+
final AtomicLong counter = entryMap.compute(entry, (e, c) -> {
297+
if (c == null) {
298+
return null;
299+
}
300+
final long count = c.decrementAndGet();
301+
return count > 0 ? c : null;
302+
});
303+
return counter != null ? counter.get() : 0L;
305304
} finally {
306305
lock.unlock();
307306
}

httpclient5/src/test/java/org/apache/hc/client5/http/impl/nio/H2SharingConnPoolTest.java

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,16 @@ void testLeaseFutureReturned() throws Exception {
8686
@Test
8787
void testLeaseExistingConnectionReturned() throws Exception {
8888
final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
89-
final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
89+
final HttpConnection conn = Mockito.mock(HttpConnection.class);
90+
Mockito.when(conn.isOpen()).thenReturn(true);
91+
poolEntry.assignConnection(conn);
92+
93+
final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool =
94+
h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
9095
routePool.track(poolEntry);
96+
final Future<PoolEntry<String, HttpConnection>> future =
97+
h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
9198

92-
final Future<PoolEntry<String, HttpConnection>> future = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
9399
Assertions.assertNotNull(future);
94100
Assertions.assertSame(poolEntry, future.get());
95101

@@ -98,8 +104,7 @@ void testLeaseExistingConnectionReturned() throws Exception {
98104
Mockito.any(),
99105
Mockito.any(),
100106
Mockito.any());
101-
Mockito.verify(callback).completed(
102-
Mockito.same(poolEntry));
107+
Mockito.verify(callback).completed(Mockito.same(poolEntry));
103108
}
104109

105110
@Test
@@ -314,38 +319,6 @@ void testReleaseReusableInCacheNotReturnedToPool() throws Exception {
314319
Mockito.anyBoolean());
315320
}
316321

317-
@Test
318-
void testReleaseNonReusableInCacheReturnedToPool() throws Exception {
319-
final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
320-
poolEntry.assignConnection(connection);
321-
Mockito.when(connection.isOpen()).thenReturn(true);
322-
final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
323-
routePool.track(poolEntry);
324-
routePool.track(poolEntry);
325-
326-
h2SharingPool.release(poolEntry, false);
327-
328-
Mockito.verify(connPool).release(
329-
Mockito.same(poolEntry),
330-
Mockito.eq(false));
331-
}
332-
333-
@Test
334-
void testReleaseReusableAndClosedInCacheReturnedToPool() throws Exception {
335-
final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
336-
poolEntry.assignConnection(connection);
337-
Mockito.when(connection.isOpen()).thenReturn(false);
338-
final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
339-
routePool.track(poolEntry);
340-
routePool.track(poolEntry);
341-
342-
h2SharingPool.release(poolEntry, true);
343-
344-
Mockito.verify(connPool).release(
345-
Mockito.same(poolEntry),
346-
Mockito.eq(true));
347-
}
348-
349322
/**
350323
* Same connection can only be released once.
351324
* Attempting to release it again will throw: IllegalStateException("Pool entry is not present in the set of leased entries")

httpclient5/src/test/java/org/apache/hc/client5/http/impl/nio/H2SharingPerRoutePoolTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ void testReleaseNonReusable() {
9898
pool.track(poolEntry1);
9999
pool.track(poolEntry1);
100100

101-
Assertions.assertEquals(0, pool.release(poolEntry1, false));
101+
Assertions.assertEquals(2, pool.release(poolEntry1, false)); // 3 → 2
102102
}
103103

104104
@Test
@@ -114,7 +114,7 @@ void testReleaseConnectionClosed() {
114114
pool.track(poolEntry1);
115115

116116
Mockito.when(poolEntry1.getConnection().isOpen()).thenReturn(false);
117-
Assertions.assertEquals(0, pool.release(poolEntry1, true));
117+
Assertions.assertEquals(2, pool.release(poolEntry1, true)); // 3 → 2
118118
}
119119

120120
@Test
@@ -124,7 +124,7 @@ void testReleaseConnectionMissing() {
124124
pool.track(poolEntry1);
125125

126126
poolEntry1.discardConnection(CloseMode.IMMEDIATE);
127-
Assertions.assertEquals(0, pool.release(poolEntry1, true));
127+
Assertions.assertEquals(2, pool.release(poolEntry1, true)); // 3 → 2
128128
}
129129

130130
}

0 commit comments

Comments
 (0)