View Javadoc
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.nio;
28  
29  import java.util.concurrent.Future;
30  import java.util.concurrent.atomic.AtomicReference;
31  
32  import org.apache.hc.core5.concurrent.BasicFuture;
33  import org.apache.hc.core5.concurrent.FutureCallback;
34  import org.apache.hc.core5.http.HttpConnection;
35  import org.apache.hc.core5.http.HttpVersion;
36  import org.apache.hc.core5.io.CloseMode;
37  import org.apache.hc.core5.pool.ManagedConnPool;
38  import org.apache.hc.core5.pool.PoolEntry;
39  import org.apache.hc.core5.util.Timeout;
40  import org.junit.jupiter.api.Assertions;
41  import org.junit.jupiter.api.BeforeEach;
42  import org.junit.jupiter.api.Test;
43  import org.mockito.Mock;
44  import org.mockito.Mockito;
45  import org.mockito.MockitoAnnotations;
46  
47  public class H2SharingConnPoolTest {
48  
49      static final String DEFAULT_ROUTE = "DEFAULT_ROUTE";
50  
51      @Mock
52      ManagedConnPool<String, HttpConnection> connPool;
53      @Mock
54      FutureCallback<PoolEntry<String, HttpConnection>> callback;
55      @Mock
56      HttpConnection connection;
57      H2SharingConnPool<String, HttpConnection> h2SharingPool;
58  
59      @BeforeEach
60      void setup() {
61          MockitoAnnotations.openMocks(this);
62          h2SharingPool = new H2SharingConnPool<>(connPool);
63      }
64  
65      @Test
66      void testLeaseFutureReturned() throws Exception {
67          Mockito.when(connPool.lease(
68                  Mockito.eq(DEFAULT_ROUTE),
69                  Mockito.any(),
70                  Mockito.any(),
71                  Mockito.any())).thenReturn(new BasicFuture<>(null));
72  
73          final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
74          Assertions.assertNotNull(result);
75          Assertions.assertFalse(result.isDone());
76  
77          Mockito.verify(connPool).lease(
78                  Mockito.eq(DEFAULT_ROUTE),
79                  Mockito.eq(null),
80                  Mockito.eq(Timeout.ONE_MILLISECOND),
81                  Mockito.any());
82          Mockito.verify(callback, Mockito.never()).completed(
83                  Mockito.any());
84      }
85  
86      @Test
87      void testLeaseExistingConnectionReturned() throws Exception {
88          final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
89          final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
90          routePool.track(poolEntry);
91  
92          final Future<PoolEntry<String, HttpConnection>> future = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
93          Assertions.assertNotNull(future);
94          Assertions.assertSame(poolEntry, future.get());
95  
96          Mockito.verify(connPool, Mockito.never()).lease(
97                  Mockito.eq(DEFAULT_ROUTE),
98                  Mockito.any(),
99                  Mockito.any(),
100                 Mockito.any());
101         Mockito.verify(callback).completed(
102                 Mockito.same(poolEntry));
103     }
104 
105     @Test
106     void testLeaseWithStateCacheBypassed() throws Exception {
107         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
108         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
109         routePool.track(poolEntry);
110 
111         Mockito.when(connPool.lease(
112                 Mockito.eq(DEFAULT_ROUTE),
113                 Mockito.any(),
114                 Mockito.any(),
115                 Mockito.any())).thenReturn(new BasicFuture<>(null));
116 
117         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, "stuff", Timeout.ONE_MILLISECOND, callback);
118         Assertions.assertNotNull(result);
119         Assertions.assertFalse(result.isDone());
120 
121         Mockito.verify(connPool).lease(
122                 Mockito.eq(DEFAULT_ROUTE),
123                 Mockito.eq("stuff"),
124                 Mockito.eq(Timeout.ONE_MILLISECOND),
125                 Mockito.any());
126         Mockito.verify(callback, Mockito.never()).completed(
127                 Mockito.any());
128     }
129 
130     @Test
131     void testLeaseNewConnectionReturnedAndCached() throws Exception {
132         final AtomicReference<BasicFuture<PoolEntry<String, HttpConnection>>> futureRef = new AtomicReference<>();
133         Mockito.when(connPool.lease(
134                 Mockito.eq(DEFAULT_ROUTE),
135                 Mockito.any(),
136                 Mockito.any(),
137                 Mockito.any())).thenAnswer(invocationOnMock -> {
138                     final BasicFuture<PoolEntry<String, HttpConnection>> future = new BasicFuture<>(invocationOnMock.getArgument(3));
139                     futureRef.set(future);
140                     return future;
141                 });
142 
143         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
144         final BasicFuture<PoolEntry<String, HttpConnection>> future = futureRef.get();
145         Assertions.assertNotNull(future);
146 
147         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
148         poolEntry.assignConnection(connection);
149         Mockito.when(connection.getProtocolVersion()).thenReturn(HttpVersion.HTTP_2);
150         future.completed(poolEntry);
151 
152         Assertions.assertTrue(result.isDone());
153 
154         Mockito.verify(connPool).lease(
155                 Mockito.eq(DEFAULT_ROUTE),
156                 Mockito.eq(null),
157                 Mockito.eq(Timeout.ONE_MILLISECOND),
158                 Mockito.any());
159         Mockito.verify(callback).completed(
160                 Mockito.any());
161 
162         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
163         Assertions.assertEquals(1, routePool.getCount(poolEntry));
164     }
165 
166     @Test
167     void testLeaseNewConnectionReturnedAndNotCached() throws Exception {
168         final AtomicReference<BasicFuture<PoolEntry<String, HttpConnection>>> futureRef = new AtomicReference<>();
169         Mockito.when(connPool.lease(
170                 Mockito.eq(DEFAULT_ROUTE),
171                 Mockito.any(),
172                 Mockito.any(),
173                 Mockito.any())).thenAnswer(invocationOnMock -> {
174             final BasicFuture<PoolEntry<String, HttpConnection>> future = new BasicFuture<>(invocationOnMock.getArgument(3));
175             futureRef.set(future);
176             return future;
177         });
178 
179         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
180         final BasicFuture<PoolEntry<String, HttpConnection>> future = futureRef.get();
181         Assertions.assertNotNull(future);
182 
183         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
184         poolEntry.assignConnection(connection);
185         Mockito.when(connection.getProtocolVersion()).thenReturn(HttpVersion.HTTP_1_1);
186         future.completed(poolEntry);
187 
188         Assertions.assertTrue(result.isDone());
189 
190         Mockito.verify(connPool).lease(
191                 Mockito.eq(DEFAULT_ROUTE),
192                 Mockito.eq(null),
193                 Mockito.eq(Timeout.ONE_MILLISECOND),
194                 Mockito.any());
195         Mockito.verify(callback).completed(
196                 Mockito.any());
197 
198         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
199         Assertions.assertEquals(0, routePool.getCount(poolEntry));
200     }
201 
202     @Test
203     void testLeaseNoConnection() throws Exception {
204         final AtomicReference<BasicFuture<PoolEntry<String, HttpConnection>>> futureRef = new AtomicReference<>();
205         Mockito.when(connPool.lease(
206                 Mockito.eq(DEFAULT_ROUTE),
207                 Mockito.any(),
208                 Mockito.any(),
209                 Mockito.any())).thenAnswer(invocationOnMock -> {
210             final BasicFuture<PoolEntry<String, HttpConnection>> future = new BasicFuture<>(invocationOnMock.getArgument(3));
211             futureRef.set(future);
212             return future;
213         });
214 
215         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
216         final BasicFuture<PoolEntry<String, HttpConnection>> future = futureRef.get();
217         Assertions.assertNotNull(future);
218 
219         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
220         poolEntry.discardConnection(CloseMode.IMMEDIATE);
221         future.completed(poolEntry);
222 
223         Assertions.assertTrue(result.isDone());
224 
225         Mockito.verify(connPool).lease(
226                 Mockito.eq(DEFAULT_ROUTE),
227                 Mockito.eq(null),
228                 Mockito.eq(Timeout.ONE_MILLISECOND),
229                 Mockito.any());
230         Mockito.verify(callback).completed(
231                 Mockito.any());
232 
233         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
234         Assertions.assertEquals(0, routePool.getCount(poolEntry));
235     }
236 
237     @Test
238     void testLeaseWithStateNewConnectionReturnedAndNotCached() throws Exception {
239         final AtomicReference<BasicFuture<PoolEntry<String, HttpConnection>>> futureRef = new AtomicReference<>();
240         Mockito.when(connPool.lease(
241                 Mockito.eq(DEFAULT_ROUTE),
242                 Mockito.any(),
243                 Mockito.any(),
244                 Mockito.any())).thenAnswer(invocationOnMock -> {
245             final BasicFuture<PoolEntry<String, HttpConnection>> future = new BasicFuture<>(invocationOnMock.getArgument(3));
246             futureRef.set(future);
247             return future;
248         });
249 
250         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, "stuff", Timeout.ONE_MILLISECOND, callback);
251         final BasicFuture<PoolEntry<String, HttpConnection>> future = futureRef.get();
252         Assertions.assertNotNull(future);
253 
254         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
255         poolEntry.assignConnection(connection);
256         Mockito.when(connection.getProtocolVersion()).thenReturn(HttpVersion.HTTP_2);
257         future.completed(poolEntry);
258 
259         Assertions.assertTrue(result.isDone());
260 
261         Mockito.verify(connPool).lease(
262                 Mockito.eq(DEFAULT_ROUTE),
263                 Mockito.eq("stuff"),
264                 Mockito.eq(Timeout.ONE_MILLISECOND),
265                 Mockito.any());
266         Mockito.verify(callback).completed(
267                 Mockito.any());
268 
269         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
270         Assertions.assertEquals(0, routePool.getCount(poolEntry));
271     }
272 
273     @Test
274     void testReleaseReusableNoCacheReturnedToPool() throws Exception {
275         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
276         poolEntry.assignConnection(connection);
277         Mockito.when(connection.isOpen()).thenReturn(true);
278 
279         h2SharingPool.release(poolEntry, true);
280 
281         Mockito.verify(connPool).release(
282                 Mockito.same(poolEntry),
283                 Mockito.eq(true));
284     }
285 
286     @Test
287     void testReleaseReusableNotInCacheReturnedToPool() throws Exception {
288         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
289         poolEntry.assignConnection(connection);
290         Mockito.when(connection.isOpen()).thenReturn(true);
291         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
292         routePool.track(poolEntry);
293 
294         h2SharingPool.release(poolEntry, true);
295 
296         Mockito.verify(connPool).release(
297                 Mockito.same(poolEntry),
298                 Mockito.eq(true));
299     }
300 
301     @Test
302     void testReleaseReusableInCacheNotReturnedToPool() throws Exception {
303         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
304         poolEntry.assignConnection(connection);
305         Mockito.when(connection.isOpen()).thenReturn(true);
306         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
307         routePool.track(poolEntry);
308         routePool.track(poolEntry);
309 
310         h2SharingPool.release(poolEntry, true);
311 
312         Mockito.verify(connPool, Mockito.never()).release(
313                 Mockito.same(poolEntry),
314                 Mockito.anyBoolean());
315     }
316 
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 
349     @Test
350     void testClose() throws Exception {
351         h2SharingPool.close();
352 
353         Mockito.verify(connPool).close();
354     }
355 
356     @Test
357     void testCloseMode() throws Exception {
358         h2SharingPool.close(CloseMode.IMMEDIATE);
359 
360         Mockito.verify(connPool).close(CloseMode.IMMEDIATE);
361     }
362 
363     @Test
364     void testLeasePoolClosed() throws Exception {
365         h2SharingPool.close();
366 
367         Assertions.assertThrows(IllegalStateException.class, () -> h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback));
368     }
369 
370     @Test
371     void testReleasePoolClosed() throws Exception {
372         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
373         poolEntry.assignConnection(connection);
374         Mockito.when(connection.isOpen()).thenReturn(false);
375         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
376         routePool.track(poolEntry);
377 
378         h2SharingPool.close();
379 
380         h2SharingPool.release(poolEntry, true);
381 
382         Mockito.verify(connPool).release(
383                 Mockito.same(poolEntry),
384                 Mockito.eq(true));
385     }
386 
387 }