Skip to content

Commit a98dd51

Browse files
committed
Off-lock disposal in PoolingHttpClientConnectionManager: enqueue GRACEFUL, run IMMEDIATE inline
Drain queue after lease/release/closeExpired/closeIdle to avoid holding the pool lock
1 parent 830b062 commit a98dd51

4 files changed

Lines changed: 442 additions & 1 deletion

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.client5.http.impl.io;
28+
29+
import java.util.Queue;
30+
import java.util.concurrent.ConcurrentLinkedQueue;
31+
32+
import org.apache.hc.core5.annotation.Internal;
33+
import org.apache.hc.core5.io.CloseMode;
34+
import org.apache.hc.core5.io.ModalCloseable;
35+
import org.apache.hc.core5.pool.DisposalCallback;
36+
37+
@Internal
38+
final class OffLockDisposalCallback<T extends ModalCloseable> implements DisposalCallback<T> {
39+
40+
private final DisposalCallback<T> delegate;
41+
private final Queue<T> gracefulQueue = new ConcurrentLinkedQueue<>();
42+
43+
OffLockDisposalCallback(final DisposalCallback<T> delegate) {
44+
this.delegate = delegate;
45+
}
46+
47+
@Override
48+
public void execute(final T closeable, final CloseMode mode) {
49+
if (mode == CloseMode.IMMEDIATE) {
50+
delegate.execute(closeable, CloseMode.IMMEDIATE);
51+
} else {
52+
gracefulQueue.offer(closeable);
53+
}
54+
}
55+
56+
void drain() {
57+
for (T c; (c = gracefulQueue.poll()) != null; ) {
58+
delegate.execute(c, CloseMode.GRACEFUL);
59+
}
60+
}
61+
}

httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ public class PoolingHttpClientConnectionManager
111111

112112
private static final Logger LOG = LoggerFactory.getLogger(PoolingHttpClientConnectionManager.class);
113113

114+
private final OffLockDisposalCallback<ManagedHttpClientConnection> disposer; // off-lock draining
115+
114116
public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 25;
115117
public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;
116118

@@ -218,14 +220,18 @@ public PoolingHttpClientConnectionManager(
218220
final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
219221
super();
220222
this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
223+
224+
// off-lock disposer wraps the default closer; we explicitly drain after pool ops
225+
this.disposer = new OffLockDisposalCallback<>(new DefaultDisposalCallback<>());
226+
221227
switch (poolConcurrencyPolicy != null ? poolConcurrencyPolicy : PoolConcurrencyPolicy.STRICT) {
222228
case STRICT:
223229
this.pool = new StrictConnPool<HttpRoute, ManagedHttpClientConnection>(
224230
DEFAULT_MAX_CONNECTIONS_PER_ROUTE,
225231
DEFAULT_MAX_TOTAL_CONNECTIONS,
226232
timeToLive,
227233
poolReusePolicy,
228-
new DefaultDisposalCallback<>(),
234+
disposer,
229235
null) {
230236

231237
@Override
@@ -240,6 +246,7 @@ public void closeExpired() {
240246
DEFAULT_MAX_CONNECTIONS_PER_ROUTE,
241247
timeToLive,
242248
poolReusePolicy,
249+
disposer,
243250
null) {
244251

245252
@Override
@@ -266,6 +273,7 @@ protected PoolingHttpClientConnectionManager(
266273
this.pool = Args.notNull(pool, "Connection pool");
267274
this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
268275
this.closed = new AtomicBoolean(false);
276+
this.disposer = null;
269277
}
270278

271279
@Override
@@ -280,6 +288,7 @@ public void close(final CloseMode closeMode) {
280288
LOG.debug("Shutdown connection pool {}", closeMode);
281289
}
282290
this.pool.close(closeMode);
291+
drainDisposals();
283292
LOG.debug("Connection pool shut down");
284293
}
285294
}
@@ -409,6 +418,7 @@ public ConnectionEndpoint get(
409418
}
410419
} finally {
411420
lock.unlock();
421+
drainDisposals();
412422
}
413423
}
414424

@@ -472,6 +482,7 @@ public void release(final ConnectionEndpoint endpoint, final Object state, final
472482
if (LOG.isDebugEnabled()) {
473483
LOG.debug("{} connection released {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.formatStats(entry.getRoute(), entry.getState(), pool));
474484
}
485+
drainDisposals();
475486
}
476487
}
477488

@@ -541,6 +552,7 @@ public void closeIdle(final TimeValue idleTime) {
541552
return;
542553
}
543554
this.pool.closeIdle(idleTime);
555+
drainDisposals();
544556
}
545557

546558
@Override
@@ -550,6 +562,7 @@ public void closeExpired() {
550562
}
551563
LOG.debug("Closing expired connections");
552564
this.pool.closeExpired();
565+
drainDisposals();
553566
}
554567

555568
@Override
@@ -837,4 +850,11 @@ public boolean isClosed() {
837850
}
838851

839852

853+
private void drainDisposals() {
854+
if (disposer != null) {
855+
disposer.drain();
856+
}
857+
}
858+
859+
840860
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.client5.http.impl.io;
28+
29+
import java.io.IOException;
30+
import java.net.InetSocketAddress;
31+
32+
import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
33+
import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
34+
import org.apache.hc.core5.http.HttpHost;
35+
import org.apache.hc.core5.http.io.SocketConfig;
36+
import org.apache.hc.core5.http.protocol.HttpContext;
37+
import org.apache.hc.core5.util.TimeValue;
38+
39+
public final class NoopOperator implements HttpClientConnectionOperator {
40+
41+
@Override
42+
public void connect(
43+
final ManagedHttpClientConnection conn,
44+
final HttpHost host,
45+
final InetSocketAddress localAddress,
46+
final TimeValue connectTimeout,
47+
final SocketConfig socketConfig,
48+
final HttpContext context) throws IOException {
49+
// no-op for tests
50+
}
51+
52+
@Override
53+
public void upgrade(
54+
final ManagedHttpClientConnection conn,
55+
final HttpHost host,
56+
final HttpContext context) throws IOException {
57+
// no-op for tests
58+
}
59+
}

0 commit comments

Comments
 (0)