@@ -191,4 +191,104 @@ public void testStreamManagerMetrics() throws Exception {
191191 CrtResource .logNativeResources ();
192192 CrtResource .waitForNoResources ();
193193 }
194+
195+ @ Test
196+ public void testMaxConcurrentStreamsEnforcement () throws Exception {
197+ skipIfAndroid ();
198+ skipIfNetworkUnavailable ();
199+
200+ URI uri = new URI (endpoint );
201+ int maxConcurrentStreams = 1 ;
202+ int numRequestsToMake = 20 ;
203+
204+ try (EventLoopGroup eventLoopGroup = new EventLoopGroup (1 );
205+ HostResolver resolver = new HostResolver (eventLoopGroup );
206+ ClientBootstrap bootstrap = new ClientBootstrap (eventLoopGroup , resolver );
207+ SocketOptions sockOpts = new SocketOptions ();
208+ TlsContextOptions tlsOpts = TlsContextOptions .createDefaultClient ().withAlpnList ("h2" );
209+ TlsContext tlsContext = createHttpClientTlsContext (tlsOpts )) {
210+
211+ Http2StreamManagerOptions options = new Http2StreamManagerOptions ();
212+ options .withMaxConcurrentStreams (maxConcurrentStreams )
213+ .withMaxConcurrentStreamsPerConnection (100 )
214+ .withIdealConcurrentStreamsPerConnection (100 );
215+
216+ HttpClientConnectionManagerOptions connectionManagerOptions = new HttpClientConnectionManagerOptions ();
217+ connectionManagerOptions .withClientBootstrap (bootstrap )
218+ .withSocketOptions (sockOpts )
219+ .withTlsContext (tlsContext )
220+ .withUri (uri )
221+ .withMaxConnections (2 );
222+ options .withConnectionManagerOptions (connectionManagerOptions );
223+
224+ Http2StreamManager streamManager = Http2StreamManager .create (options );
225+
226+ try {
227+ Http2Request request = createHttp2Request ("GET" , endpoint , path , EMPTY_BODY );
228+
229+ final AtomicInteger completedRequests = new AtomicInteger (0 );
230+ final AtomicInteger maxConcurrentActive = new AtomicInteger (0 );
231+ final AtomicInteger currentActive = new AtomicInteger (0 );
232+ final CountDownLatch allRequestsComplete = new CountDownLatch (numRequestsToMake );
233+
234+ // Acquire multiple streams rapidly
235+ for (int i = 0 ; i < numRequestsToMake ; i ++) {
236+ streamManager .acquireStream (request , new HttpStreamBaseResponseHandler () {
237+ @ Override
238+ public void onResponseHeaders (HttpStreamBase stream , int responseStatusCode , int blockType ,
239+ HttpHeader [] nextHeaders ) {
240+ // Track concurrent active streams
241+ int active = currentActive .incrementAndGet ();
242+ int prevMax = maxConcurrentActive .get ();
243+ while (active > prevMax ) {
244+ if (maxConcurrentActive .compareAndSet (prevMax , active )) {
245+ break ;
246+ }
247+ prevMax = maxConcurrentActive .get ();
248+ }
249+ }
250+
251+ @ Override
252+ public void onResponseComplete (HttpStreamBase stream , int errorCode ) {
253+ currentActive .decrementAndGet ();
254+ completedRequests .incrementAndGet ();
255+ stream .close ();
256+ allRequestsComplete .countDown ();
257+ }
258+ }).whenComplete ((stream , throwable ) -> {
259+ if (throwable != null ) {
260+ allRequestsComplete .countDown ();
261+ }
262+ });
263+ }
264+
265+ // Wait for all requests to complete (with timeout)
266+ boolean completed = allRequestsComplete .await (60 , TimeUnit .SECONDS );
267+ Assert .assertTrue ("Requests did not complete within timeout" , completed );
268+
269+ // Verify all requests completed
270+ Assert .assertEquals (numRequestsToMake , completedRequests .get ());
271+
272+ // Verify that max concurrent streams limit was respected
273+ // The actual concurrent count should not exceed our limit significantly
274+ // (allowing small margin for timing)
275+ int observedMax = maxConcurrentActive .get ();
276+ Log .log (Log .LogLevel .Info , Log .LogSubject .HttpConnectionManager ,
277+ String .format ("Max concurrent streams observed: %d (limit: %d)" , observedMax , maxConcurrentStreams ));
278+
279+ // The observed max should be close to our limit (allowing some flexibility for race conditions)
280+ Assert .assertTrue (
281+ String .format ("Expected max concurrent streams around %d, but observed %d" ,
282+ maxConcurrentStreams , observedMax ),
283+ observedMax <= maxConcurrentStreams );
284+
285+ } finally {
286+ streamManager .close ();
287+ streamManager .getShutdownCompleteFuture ().get (60 , TimeUnit .SECONDS );
288+ }
289+ }
290+
291+ CrtResource .logNativeResources ();
292+ CrtResource .waitForNoResources ();
293+ }
194294}
0 commit comments