3232import java .nio .ByteBuffer ;
3333import java .util .List ;
3434import java .util .Set ;
35+ import java .util .concurrent .ConcurrentHashMap ;
36+ import java .util .concurrent .ConcurrentMap ;
3537import java .util .concurrent .Future ;
38+ import java .util .concurrent .RejectedExecutionException ;
39+ import java .util .concurrent .atomic .AtomicInteger ;
3640
3741import org .apache .hc .core5 .annotation .Internal ;
3842import org .apache .hc .core5 .concurrent .Cancellable ;
4347import org .apache .hc .core5 .function .Callback ;
4448import org .apache .hc .core5 .function .Decorator ;
4549import org .apache .hc .core5 .function .Resolver ;
46- import org .apache .hc .core5 .http .ConnectionClosedException ;
4750import org .apache .hc .core5 .http .EntityDetails ;
4851import org .apache .hc .core5 .http .Header ;
4952import org .apache .hc .core5 .http .HttpException ;
8689public class H2MultiplexingRequester extends AsyncRequester {
8790
8891 private final H2ConnPool connPool ;
92+ private final ConcurrentMap <IOSession , AtomicInteger > inFlightPerSession ;
93+ private final int maxRequestsPerConnection ;
8994
9095 /**
9196 * Use {@link H2MultiplexingRequesterBootstrap} to create instances of this class.
@@ -100,11 +105,44 @@ public H2MultiplexingRequester(
100105 final Resolver <HttpHost , InetSocketAddress > addressResolver ,
101106 final TlsStrategy tlsStrategy ,
102107 final IOReactorMetricsListener threadPoolListener ,
103- final IOWorkerSelector workerSelector ) {
108+ final IOWorkerSelector workerSelector ,
109+ final int maxRequestsPerConnection ) {
104110 super (eventHandlerFactory , ioReactorConfig , ioSessionDecorator , exceptionCallback , sessionListener ,
105111 ShutdownCommand .GRACEFUL_IMMEDIATE_CALLBACK , DefaultAddressResolver .INSTANCE ,
106112 threadPoolListener , workerSelector );
107113 this .connPool = new H2ConnPool (this , addressResolver , tlsStrategy );
114+ this .inFlightPerSession = new ConcurrentHashMap <>();
115+ this .maxRequestsPerConnection = maxRequestsPerConnection ;
116+ }
117+
118+ private boolean tryAcquireSlot (final IOSession ioSession , final int max ) {
119+ if (max <= 0 ) {
120+ return true ;
121+ }
122+ final AtomicInteger counter = inFlightPerSession .computeIfAbsent (ioSession , s -> new AtomicInteger (0 ));
123+ for (;;) {
124+ final int q = counter .get ();
125+ if (q >= max ) {
126+ return false ;
127+ }
128+ if (counter .compareAndSet (q , q + 1 )) {
129+ return true ;
130+ }
131+ }
132+ }
133+
134+ private void releaseSlot (final IOSession ioSession , final int max ) {
135+ if (max <= 0 ) {
136+ return ;
137+ }
138+ final AtomicInteger counter = inFlightPerSession .get (ioSession );
139+ if (counter == null ) {
140+ return ;
141+ }
142+ final int q = counter .decrementAndGet ();
143+ if (q <= 0 ) {
144+ inFlightPerSession .remove (ioSession , counter );
145+ }
108146 }
109147
110148 public void closeIdle (final TimeValue idleTime ) {
@@ -182,15 +220,29 @@ private void execute(
182220 if (request .getAuthority () == null ) {
183221 request .setAuthority (new URIAuthority (host ));
184222 }
223+ if (request .getScheme () == null ) {
224+ request .setScheme (host .getSchemeName ());
225+ }
185226 connPool .getSession (host , timeout , new FutureCallback <IOSession >() {
186227
187228 @ Override
188229 public void completed (final IOSession ioSession ) {
230+ if (!tryAcquireSlot (ioSession , maxRequestsPerConnection )) {
231+ exchangeHandler .failed (new RejectedExecutionException (
232+ "Maximum number of concurrent requests per connection reached (max=" + maxRequestsPerConnection + ")" ));
233+ exchangeHandler .releaseResources ();
234+ return ;
235+ }
236+
237+ final AsyncClientExchangeHandler actual = maxRequestsPerConnection > 0
238+ ? new ReleasingAsyncClientExchangeHandler (exchangeHandler , () -> releaseSlot (ioSession , maxRequestsPerConnection ))
239+ : exchangeHandler ;
240+
189241 final AsyncClientExchangeHandler handlerProxy = new AsyncClientExchangeHandler () {
190242
191243 @ Override
192244 public void releaseResources () {
193- exchangeHandler .releaseResources ();
245+ actual .releaseResources ();
194246 }
195247
196248 @ Override
@@ -199,67 +251,70 @@ public void produceRequest(final RequestChannel channel, final HttpContext httpC
199251 }
200252
201253 @ Override
202- public int available () {
203- return exchangeHandler .available ();
254+ public void consumeResponse (
255+ final HttpResponse response , final EntityDetails entityDetails , final HttpContext httpContext ) throws HttpException , IOException {
256+ actual .consumeResponse (response , entityDetails , httpContext );
204257 }
205258
206259 @ Override
207- public void produce (final DataStreamChannel channel ) throws IOException {
208- exchangeHandler . produce ( channel );
260+ public void consumeInformation (final HttpResponse response , final HttpContext httpContext ) throws HttpException , IOException {
261+ actual . consumeInformation ( response , httpContext );
209262 }
210263
211264 @ Override
212- public void consumeInformation ( final HttpResponse response , final HttpContext httpContext ) throws HttpException , IOException {
213- exchangeHandler . consumeInformation ( response , httpContext );
265+ public int available () {
266+ return actual . available ( );
214267 }
215268
216269 @ Override
217- public void consumeResponse (
218- final HttpResponse response , final EntityDetails entityDetails , final HttpContext httpContext ) throws HttpException , IOException {
219- exchangeHandler .consumeResponse (response , entityDetails , httpContext );
270+ public void produce (final DataStreamChannel channel ) throws IOException {
271+ actual .produce (channel );
220272 }
221273
222274 @ Override
223275 public void updateCapacity (final CapacityChannel capacityChannel ) throws IOException {
224- exchangeHandler .updateCapacity (capacityChannel );
276+ actual .updateCapacity (capacityChannel );
225277 }
226278
227279 @ Override
228280 public void consume (final ByteBuffer src ) throws IOException {
229- exchangeHandler .consume (src );
281+ actual .consume (src );
230282 }
231283
232284 @ Override
233285 public void streamEnd (final List <? extends Header > trailers ) throws HttpException , IOException {
234- exchangeHandler .streamEnd (trailers );
286+ actual .streamEnd (trailers );
235287 }
236288
237289 @ Override
238290 public void cancel () {
239- exchangeHandler .cancel ();
291+ actual .cancel ();
240292 }
241293
242294 @ Override
243295 public void failed (final Exception cause ) {
244- exchangeHandler .failed (cause );
296+ actual .failed (cause );
245297 }
246298
247299 };
248300 final Timeout socketTimeout = ioSession .getSocketTimeout ();
249- ioSession .enqueue (new RequestExecutionCommand (
250- handlerProxy ,
251- pushHandlerFactory ,
252- context ,
253- streamControl -> {
254- cancellableDependency .setDependency (streamControl );
255- if (socketTimeout != null ) {
256- streamControl .setTimeout (socketTimeout );
257- }
258- }),
259- Command .Priority .NORMAL );
260- if (!ioSession .isOpen ()) {
261- exchangeHandler .failed (new ConnectionClosedException ());
301+ try {
302+ ioSession .enqueue (new RequestExecutionCommand (
303+ handlerProxy ,
304+ pushHandlerFactory ,
305+ context ,
306+ streamControl -> {
307+ cancellableDependency .setDependency (streamControl );
308+ if (socketTimeout != null ) {
309+ streamControl .setTimeout (socketTimeout );
310+ }
311+ }),
312+ Command .Priority .NORMAL );
313+ } catch (final RuntimeException ex ) {
314+ actual .failed (ex );
315+ actual .releaseResources ();
262316 }
317+
263318 }
264319
265320 @ Override
0 commit comments