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.core5.reactor;
28  
29  import java.net.UnknownHostException;
30  import java.util.concurrent.Future;
31  
32  import org.apache.hc.core5.concurrent.FutureCallback;
33  import org.apache.hc.core5.function.Callback;
34  import org.apache.hc.core5.io.CloseMode;
35  import org.apache.hc.core5.util.TimeValue;
36  import org.apache.hc.core5.util.Timeout;
37  import org.hamcrest.CoreMatchers;
38  import org.hamcrest.MatcherAssert;
39  import org.junit.Assert;
40  import org.junit.Before;
41  import org.junit.Test;
42  import org.junit.runner.RunWith;
43  import org.mockito.Answers;
44  import org.mockito.ArgumentCaptor;
45  import org.mockito.ArgumentMatcher;
46  import org.mockito.ArgumentMatchers;
47  import org.mockito.Captor;
48  import org.mockito.Mock;
49  import org.mockito.Mockito;
50  import org.mockito.invocation.InvocationOnMock;
51  import org.mockito.junit.MockitoJUnitRunner;
52  import org.mockito.stubbing.Answer;
53  
54  @RunWith(MockitoJUnitRunner.class)
55  public class TestAbstractIOSessionPool {
56  
57      @Mock
58      private Future<IOSession> connectFuture;
59      @Mock
60      private FutureCallback<IOSession> callback1;
61      @Mock
62      private FutureCallback<IOSession> callback2;
63      @Mock
64      private IOSession ioSession1;
65      @Mock
66      private IOSession ioSession2;
67      @Captor
68      ArgumentCaptor<FutureCallback<IOSession>> connectCallbackCaptor;
69  
70      private AbstractIOSessionPool<String> impl;
71  
72      @Before
73      public void setup() {
74          impl = Mockito.mock(AbstractIOSessionPool.class, Mockito.withSettings()
75                  .defaultAnswer(Answers.CALLS_REAL_METHODS)
76                  .useConstructor());
77      }
78  
79      @Test
80      public void testGetSessions() throws Exception {
81  
82          Mockito.when(impl.connectSession(
83                  ArgumentMatchers.anyString(),
84                  ArgumentMatchers.<Timeout>any(),
85                  ArgumentMatchers.<FutureCallback<IOSession>>any())).thenReturn(connectFuture);
86  
87          Mockito.doAnswer(new Answer() {
88  
89              @Override
90              public Object answer(final InvocationOnMock invocation) throws Throwable {
91                  final Callback<Boolean> callback = invocation.getArgument(1);
92                  callback.execute(true);
93                  return null;
94              }
95  
96          }).when(impl).validateSession(ArgumentMatchers.<IOSession>any(), ArgumentMatchers.<Callback<Boolean>>any());
97  
98          Mockito.when(ioSession1.isOpen()).thenReturn(true);
99  
100         final Future<IOSession> future1 = impl.getSession("somehost", Timeout.ofSeconds(123L), null);
101         MatcherAssert.assertThat(future1, CoreMatchers.notNullValue());
102         MatcherAssert.assertThat(future1.isDone(), CoreMatchers.equalTo(false));
103         MatcherAssert.assertThat(impl.getRoutes(), CoreMatchers.hasItem("somehost"));
104 
105         Mockito.verify(impl).connectSession(
106                 ArgumentMatchers.eq("somehost"),
107                 ArgumentMatchers.eq(Timeout.ofSeconds(123L)),
108                 ArgumentMatchers.<FutureCallback<IOSession>>any());
109 
110         final Future<IOSession> future2 = impl.getSession("somehost", Timeout.ofSeconds(123L), null);
111         MatcherAssert.assertThat(future2, CoreMatchers.notNullValue());
112         MatcherAssert.assertThat(future2.isDone(), CoreMatchers.equalTo(false));
113         MatcherAssert.assertThat(impl.getRoutes(), CoreMatchers.hasItem("somehost"));
114 
115         Mockito.verify(impl, Mockito.times(1)).connectSession(
116                 ArgumentMatchers.eq("somehost"),
117                 ArgumentMatchers.<Timeout>any(),
118                 ArgumentMatchers.argThat(new ArgumentMatcher<FutureCallback<IOSession>>() {
119 
120                     @Override
121                     public boolean matches(final FutureCallback<IOSession> callback) {
122                         callback.completed(ioSession1);
123                         return true;
124                     }
125 
126                 }));
127 
128         MatcherAssert.assertThat(future1.isDone(), CoreMatchers.equalTo(true));
129         MatcherAssert.assertThat(future1.get(), CoreMatchers.sameInstance(ioSession1));
130 
131         MatcherAssert.assertThat(future2.isDone(), CoreMatchers.equalTo(true));
132         MatcherAssert.assertThat(future2.get(), CoreMatchers.sameInstance(ioSession1));
133 
134         Mockito.verify(impl, Mockito.times(2)).validateSession(ArgumentMatchers.<IOSession>any(), ArgumentMatchers.<Callback<Boolean>>any());
135 
136         final Future<IOSession> future3 = impl.getSession("somehost", Timeout.ofSeconds(123L), null);
137 
138         Mockito.verify(impl, Mockito.times(1)).connectSession(
139                 ArgumentMatchers.eq("somehost"),
140                 ArgumentMatchers.<Timeout>any(),
141                 ArgumentMatchers.<FutureCallback<IOSession>>any());
142 
143         Mockito.verify(impl, Mockito.times(3)).validateSession(ArgumentMatchers.<IOSession>any(), ArgumentMatchers.<Callback<Boolean>>any());
144 
145         MatcherAssert.assertThat(future3.isDone(), CoreMatchers.equalTo(true));
146         MatcherAssert.assertThat(future3.get(), CoreMatchers.sameInstance(ioSession1));
147     }
148 
149     @Test
150     public void testGetSessionConnectFailure() throws Exception {
151 
152         Mockito.when(impl.connectSession(
153                 ArgumentMatchers.anyString(),
154                 ArgumentMatchers.<Timeout>any(),
155                 ArgumentMatchers.<FutureCallback<IOSession>>any())).thenReturn(connectFuture);
156 
157         final Future<IOSession> future1 = impl.getSession("somehost", Timeout.ofSeconds(123L), null);
158         MatcherAssert.assertThat(future1, CoreMatchers.notNullValue());
159         MatcherAssert.assertThat(future1.isDone(), CoreMatchers.equalTo(false));
160         MatcherAssert.assertThat(impl.getRoutes(), CoreMatchers.hasItem("somehost"));
161 
162         Mockito.verify(impl).connectSession(
163                 ArgumentMatchers.eq("somehost"),
164                 ArgumentMatchers.eq(Timeout.ofSeconds(123L)),
165                 connectCallbackCaptor.capture());
166 
167         final Future<IOSession> future2 = impl.getSession("somehost", Timeout.ofSeconds(123L), null);
168         MatcherAssert.assertThat(future2, CoreMatchers.notNullValue());
169         MatcherAssert.assertThat(future2.isDone(), CoreMatchers.equalTo(false));
170         MatcherAssert.assertThat(impl.getRoutes(), CoreMatchers.hasItem("somehost"));
171 
172         final FutureCallback<IOSession> connectCallback = connectCallbackCaptor.getValue();
173         Assert.assertNotNull(connectCallback);
174         connectCallback.failed(new Exception("Boom"));
175 
176         // Ensure connect failure invalidates all pending futures
177         MatcherAssert.assertThat(future1.isDone(), CoreMatchers.equalTo(true));
178         MatcherAssert.assertThat(future2.isDone(), CoreMatchers.equalTo(true));
179     }
180 
181     @Test
182     public void testShutdownPool() throws Exception {
183         final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1");
184         MatcherAssert.assertThat(entry1, CoreMatchers.notNullValue());
185         entry1.session = ioSession1;
186 
187         final AbstractIOSessionPool.PoolEntry entry2 = impl.getPoolEntry("host2");
188         MatcherAssert.assertThat(entry2, CoreMatchers.notNullValue());
189         entry2.session = ioSession2;
190 
191         final AbstractIOSessionPool.PoolEntry entry3 = impl.getPoolEntry("host3");
192         MatcherAssert.assertThat(entry3, CoreMatchers.notNullValue());
193         entry3.sessionFuture = connectFuture;
194         entry3.requestQueue.add(callback1);
195         entry3.requestQueue.add(callback2);
196 
197         impl.close(CloseMode.GRACEFUL);
198 
199         Mockito.verify(impl).closeSession(ioSession1, CloseMode.GRACEFUL);
200         Mockito.verify(impl).closeSession(ioSession2, CloseMode.GRACEFUL);
201         Mockito.verify(connectFuture).cancel(ArgumentMatchers.anyBoolean());
202         Mockito.verify(callback1).cancelled();
203         Mockito.verify(callback2).cancelled();
204     }
205 
206     @Test
207     public void testCloseIdleSessions() throws Exception {
208         final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1");
209         MatcherAssert.assertThat(entry1, CoreMatchers.notNullValue());
210         entry1.session = ioSession1;
211 
212         final AbstractIOSessionPool.PoolEntry entry2 = impl.getPoolEntry("host2");
213         MatcherAssert.assertThat(entry2, CoreMatchers.notNullValue());
214         entry2.session = ioSession2;
215 
216         impl.closeIdle(TimeValue.ZERO_MILLISECONDS);
217 
218         Mockito.verify(impl).closeSession(ioSession1, CloseMode.GRACEFUL);
219         Mockito.verify(impl).closeSession(ioSession2, CloseMode.GRACEFUL);
220 
221         MatcherAssert.assertThat(entry1.session, CoreMatchers.nullValue());
222         MatcherAssert.assertThat(entry2.session, CoreMatchers.nullValue());
223     }
224 
225     @Test
226     public void testEnumSessions() throws Exception {
227         final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("host1");
228         MatcherAssert.assertThat(entry1, CoreMatchers.notNullValue());
229         entry1.session = ioSession1;
230 
231         final AbstractIOSessionPool.PoolEntry entry2 = impl.getPoolEntry("host2");
232         MatcherAssert.assertThat(entry2, CoreMatchers.notNullValue());
233         entry2.session = ioSession2;
234 
235         impl.enumAvailable(new Callback<IOSession>() {
236 
237             @Override
238             public void execute(final IOSession ioSession) {
239                 ioSession.close(CloseMode.GRACEFUL);
240             }
241 
242         });
243         Mockito.verify(ioSession1).close(CloseMode.GRACEFUL);
244         Mockito.verify(ioSession2).close(CloseMode.GRACEFUL);
245     }
246 
247     @Test
248     public void testGetSessionReconnectAfterValidate() throws Exception {
249         final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("somehost");
250         MatcherAssert.assertThat(entry1, CoreMatchers.notNullValue());
251         entry1.session = ioSession1;
252 
253         Mockito.when(ioSession1.isOpen()).thenReturn(true);
254         Mockito.doAnswer(new Answer() {
255 
256             @Override
257             public Object answer(final InvocationOnMock invocation) throws Throwable {
258                 final Callback<Boolean> callback = invocation.getArgument(1);
259                 callback.execute(false);
260                 return null;
261             }
262 
263         }).when(impl).validateSession(ArgumentMatchers.<IOSession>any(), ArgumentMatchers.<Callback<Boolean>>any());
264 
265         impl.getSession("somehost", Timeout.ofSeconds(123L), null);
266 
267         Mockito.verify(impl, Mockito.times(1)).connectSession(
268                 ArgumentMatchers.eq("somehost"),
269                 ArgumentMatchers.eq(Timeout.ofSeconds(123L)),
270                 ArgumentMatchers.<FutureCallback<IOSession>>any());
271     }
272 
273     @Test
274     public void testGetSessionReconnectIfClosed() throws Exception {
275         final AbstractIOSessionPool.PoolEntry entry1 = impl.getPoolEntry("somehost");
276         MatcherAssert.assertThat(entry1, CoreMatchers.notNullValue());
277         entry1.session = ioSession1;
278 
279         Mockito.when(ioSession1.isOpen()).thenReturn(false);
280 
281         impl.getSession("somehost", Timeout.ofSeconds(123L), null);
282 
283         Mockito.verify(impl).connectSession(
284                 ArgumentMatchers.eq("somehost"),
285                 ArgumentMatchers.eq(Timeout.ofSeconds(123L)),
286                 ArgumentMatchers.<FutureCallback<IOSession>>any());
287     }
288 
289     @Test
290     public void testGetSessionConnectUnknownHost() throws Exception {
291 
292         Mockito.when(connectFuture.isDone()).thenReturn(true);
293         Mockito.when(impl.connectSession(
294                 ArgumentMatchers.anyString(),
295                 ArgumentMatchers.<Timeout>any(),
296                 ArgumentMatchers.argThat(new ArgumentMatcher<FutureCallback<IOSession>>() {
297 
298                     @Override
299                     public boolean matches(final FutureCallback<IOSession> callback) {
300                         callback.failed(new UnknownHostException("Boom"));
301                         return true;
302                     }
303 
304                 }))).thenReturn(connectFuture);
305 
306         final Future<IOSession> future1 = impl.getSession("somehost", Timeout.ofSeconds(123L), null);
307         MatcherAssert.assertThat(future1, CoreMatchers.notNullValue());
308         MatcherAssert.assertThat(future1.isDone(), CoreMatchers.equalTo(true));
309 
310         final Future<IOSession> future2 = impl.getSession("somehost", Timeout.ofSeconds(123L), null);
311         MatcherAssert.assertThat(future2, CoreMatchers.notNullValue());
312         MatcherAssert.assertThat(future2.isDone(), CoreMatchers.equalTo(true));
313     }
314 
315 }