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 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);
95          routePool.track(poolEntry);
96          final Future<PoolEntry<String, HttpConnection>> future =
97                  h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
98  
99          Assertions.assertNotNull(future);
100         Assertions.assertSame(poolEntry, future.get());
101 
102         Mockito.verify(connPool, Mockito.never()).lease(
103                 Mockito.eq(DEFAULT_ROUTE),
104                 Mockito.any(),
105                 Mockito.any(),
106                 Mockito.any());
107         Mockito.verify(callback).completed(Mockito.same(poolEntry));
108     }
109 
110     @Test
111     void testLeaseWithStateCacheBypassed() throws Exception {
112         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
113         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
114         routePool.track(poolEntry);
115 
116         Mockito.when(connPool.lease(
117                 Mockito.eq(DEFAULT_ROUTE),
118                 Mockito.any(),
119                 Mockito.any(),
120                 Mockito.any())).thenReturn(new BasicFuture<>(null));
121 
122         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, "stuff", Timeout.ONE_MILLISECOND, callback);
123         Assertions.assertNotNull(result);
124         Assertions.assertFalse(result.isDone());
125 
126         Mockito.verify(connPool).lease(
127                 Mockito.eq(DEFAULT_ROUTE),
128                 Mockito.eq("stuff"),
129                 Mockito.eq(Timeout.ONE_MILLISECOND),
130                 Mockito.any());
131         Mockito.verify(callback, Mockito.never()).completed(
132                 Mockito.any());
133     }
134 
135     @Test
136     void testLeaseNewConnectionReturnedAndCached() throws Exception {
137         final AtomicReference<BasicFuture<PoolEntry<String, HttpConnection>>> futureRef = new AtomicReference<>();
138         Mockito.when(connPool.lease(
139                 Mockito.eq(DEFAULT_ROUTE),
140                 Mockito.any(),
141                 Mockito.any(),
142                 Mockito.any())).thenAnswer(invocationOnMock -> {
143                     final BasicFuture<PoolEntry<String, HttpConnection>> future = new BasicFuture<>(invocationOnMock.getArgument(3));
144                     futureRef.set(future);
145                     return future;
146                 });
147 
148         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
149         final BasicFuture<PoolEntry<String, HttpConnection>> future = futureRef.get();
150         Assertions.assertNotNull(future);
151 
152         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
153         poolEntry.assignConnection(connection);
154         Mockito.when(connection.getProtocolVersion()).thenReturn(HttpVersion.HTTP_2);
155         future.completed(poolEntry);
156 
157         Assertions.assertTrue(result.isDone());
158 
159         Mockito.verify(connPool).lease(
160                 Mockito.eq(DEFAULT_ROUTE),
161                 Mockito.eq(null),
162                 Mockito.eq(Timeout.ONE_MILLISECOND),
163                 Mockito.any());
164         Mockito.verify(callback).completed(
165                 Mockito.any());
166 
167         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
168         Assertions.assertEquals(1, routePool.getCount(poolEntry));
169     }
170 
171     @Test
172     void testLeaseNewConnectionReturnedAndNotCached() throws Exception {
173         final AtomicReference<BasicFuture<PoolEntry<String, HttpConnection>>> futureRef = new AtomicReference<>();
174         Mockito.when(connPool.lease(
175                 Mockito.eq(DEFAULT_ROUTE),
176                 Mockito.any(),
177                 Mockito.any(),
178                 Mockito.any())).thenAnswer(invocationOnMock -> {
179             final BasicFuture<PoolEntry<String, HttpConnection>> future = new BasicFuture<>(invocationOnMock.getArgument(3));
180             futureRef.set(future);
181             return future;
182         });
183 
184         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
185         final BasicFuture<PoolEntry<String, HttpConnection>> future = futureRef.get();
186         Assertions.assertNotNull(future);
187 
188         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
189         poolEntry.assignConnection(connection);
190         Mockito.when(connection.getProtocolVersion()).thenReturn(HttpVersion.HTTP_1_1);
191         future.completed(poolEntry);
192 
193         Assertions.assertTrue(result.isDone());
194 
195         Mockito.verify(connPool).lease(
196                 Mockito.eq(DEFAULT_ROUTE),
197                 Mockito.eq(null),
198                 Mockito.eq(Timeout.ONE_MILLISECOND),
199                 Mockito.any());
200         Mockito.verify(callback).completed(
201                 Mockito.any());
202 
203         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
204         Assertions.assertEquals(0, routePool.getCount(poolEntry));
205     }
206 
207     @Test
208     void testLeaseNoConnection() throws Exception {
209         final AtomicReference<BasicFuture<PoolEntry<String, HttpConnection>>> futureRef = new AtomicReference<>();
210         Mockito.when(connPool.lease(
211                 Mockito.eq(DEFAULT_ROUTE),
212                 Mockito.any(),
213                 Mockito.any(),
214                 Mockito.any())).thenAnswer(invocationOnMock -> {
215             final BasicFuture<PoolEntry<String, HttpConnection>> future = new BasicFuture<>(invocationOnMock.getArgument(3));
216             futureRef.set(future);
217             return future;
218         });
219 
220         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback);
221         final BasicFuture<PoolEntry<String, HttpConnection>> future = futureRef.get();
222         Assertions.assertNotNull(future);
223 
224         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
225         poolEntry.discardConnection(CloseMode.IMMEDIATE);
226         future.completed(poolEntry);
227 
228         Assertions.assertTrue(result.isDone());
229 
230         Mockito.verify(connPool).lease(
231                 Mockito.eq(DEFAULT_ROUTE),
232                 Mockito.eq(null),
233                 Mockito.eq(Timeout.ONE_MILLISECOND),
234                 Mockito.any());
235         Mockito.verify(callback).completed(
236                 Mockito.any());
237 
238         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
239         Assertions.assertEquals(0, routePool.getCount(poolEntry));
240     }
241 
242     @Test
243     void testLeaseWithStateNewConnectionReturnedAndNotCached() throws Exception {
244         final AtomicReference<BasicFuture<PoolEntry<String, HttpConnection>>> futureRef = new AtomicReference<>();
245         Mockito.when(connPool.lease(
246                 Mockito.eq(DEFAULT_ROUTE),
247                 Mockito.any(),
248                 Mockito.any(),
249                 Mockito.any())).thenAnswer(invocationOnMock -> {
250             final BasicFuture<PoolEntry<String, HttpConnection>> future = new BasicFuture<>(invocationOnMock.getArgument(3));
251             futureRef.set(future);
252             return future;
253         });
254 
255         final Future<PoolEntry<String, HttpConnection>> result = h2SharingPool.lease(DEFAULT_ROUTE, "stuff", Timeout.ONE_MILLISECOND, callback);
256         final BasicFuture<PoolEntry<String, HttpConnection>> future = futureRef.get();
257         Assertions.assertNotNull(future);
258 
259         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
260         poolEntry.assignConnection(connection);
261         Mockito.when(connection.getProtocolVersion()).thenReturn(HttpVersion.HTTP_2);
262         future.completed(poolEntry);
263 
264         Assertions.assertTrue(result.isDone());
265 
266         Mockito.verify(connPool).lease(
267                 Mockito.eq(DEFAULT_ROUTE),
268                 Mockito.eq("stuff"),
269                 Mockito.eq(Timeout.ONE_MILLISECOND),
270                 Mockito.any());
271         Mockito.verify(callback).completed(
272                 Mockito.any());
273 
274         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
275         Assertions.assertEquals(0, routePool.getCount(poolEntry));
276     }
277 
278     @Test
279     void testReleaseReusableNoCacheReturnedToPool() throws Exception {
280         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
281         poolEntry.assignConnection(connection);
282         Mockito.when(connection.isOpen()).thenReturn(true);
283 
284         h2SharingPool.release(poolEntry, true);
285 
286         Mockito.verify(connPool).release(
287                 Mockito.same(poolEntry),
288                 Mockito.eq(true));
289     }
290 
291     @Test
292     void testReleaseReusableNotInCacheReturnedToPool() throws Exception {
293         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
294         poolEntry.assignConnection(connection);
295         Mockito.when(connection.isOpen()).thenReturn(true);
296         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
297         routePool.track(poolEntry);
298 
299         h2SharingPool.release(poolEntry, true);
300 
301         Mockito.verify(connPool).release(
302                 Mockito.same(poolEntry),
303                 Mockito.eq(true));
304     }
305 
306     @Test
307     void testReleaseReusableInCacheNotReturnedToPool() throws Exception {
308         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
309         poolEntry.assignConnection(connection);
310         Mockito.when(connection.isOpen()).thenReturn(true);
311         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
312         routePool.track(poolEntry);
313         routePool.track(poolEntry);
314 
315         h2SharingPool.release(poolEntry, true);
316 
317         Mockito.verify(connPool, Mockito.never()).release(
318                 Mockito.same(poolEntry),
319                 Mockito.anyBoolean());
320     }
321 
322     /**
323      * Same connection can only be released once.
324      * Attempting to release it again will throw: IllegalStateException("Pool entry is not present in the set of leased entries")
325      *
326      * @see org.apache.hc.core5.pool.LaxConnPool.PerRoutePool#removeLeased(PoolEntry)
327      * @see org.apache.hc.core5.pool.StrictConnPool#release(PoolEntry, boolean)
328      */
329     @Test
330     void testReleaseNonReusableNotInCacheReturnedToPool() throws Exception {
331         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
332         poolEntry.assignConnection(connection);
333         Mockito.when(connection.isOpen()).thenReturn(false);
334         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
335         routePool.track(poolEntry);
336         routePool.track(poolEntry);
337 
338         final AtomicReference<HttpConnection> connRef = new AtomicReference<>(connection);
339         Mockito.doAnswer(invocation -> {
340             final PoolEntry<String, HttpConnection> entry = invocation.getArgument(0);
341             if (!connRef.compareAndSet(entry.getConnection(), null)) {
342                 throw new IllegalStateException("Pool entry is not present in the set of leased entries");
343             }
344             return null;
345         }).when(connPool).release(Mockito.eq(poolEntry), Mockito.anyBoolean());
346 
347         h2SharingPool.release(poolEntry, false);
348         // for reproduce https://issues.apache.org/jira/browse/HTTPCLIENT-2379
349         Assertions.assertThrows(IllegalStateException.class, () -> h2SharingPool.release(poolEntry, false));
350     }
351 
352     @Test
353     void testClose() throws Exception {
354         h2SharingPool.close();
355 
356         Mockito.verify(connPool).close();
357     }
358 
359     @Test
360     void testCloseMode() throws Exception {
361         h2SharingPool.close(CloseMode.IMMEDIATE);
362 
363         Mockito.verify(connPool).close(CloseMode.IMMEDIATE);
364     }
365 
366     @Test
367     void testLeasePoolClosed() throws Exception {
368         h2SharingPool.close();
369 
370         Assertions.assertThrows(IllegalStateException.class, () -> h2SharingPool.lease(DEFAULT_ROUTE, null, Timeout.ONE_MILLISECOND, callback));
371     }
372 
373     @Test
374     void testReleasePoolClosed() throws Exception {
375         final PoolEntry<String, HttpConnection> poolEntry = new PoolEntry<>(DEFAULT_ROUTE);
376         poolEntry.assignConnection(connection);
377         Mockito.when(connection.isOpen()).thenReturn(false);
378         final H2SharingConnPool.PerRoutePool<String, HttpConnection> routePool = h2SharingPool.getPerRoutePool(DEFAULT_ROUTE);
379         routePool.track(poolEntry);
380 
381         h2SharingPool.close();
382 
383         h2SharingPool.release(poolEntry, true);
384 
385         Mockito.verify(connPool).release(
386                 Mockito.same(poolEntry),
387                 Mockito.eq(true));
388     }
389 
390 }