1
1
package ru .yandex .clickhouse .util ;
2
2
3
3
import org .apache .http .HttpHost ;
4
+ import org .apache .http .HttpResponse ;
4
5
import org .apache .http .NoHttpResponseException ;
5
6
import org .apache .http .client .methods .HttpGet ;
6
7
import org .apache .http .client .methods .HttpPost ;
7
8
import org .apache .http .conn .HttpHostConnectException ;
8
9
import org .apache .http .impl .client .CloseableHttpClient ;
9
10
import org .apache .http .protocol .BasicHttpContext ;
10
11
import org .apache .http .protocol .HttpContext ;
12
+ import org .apache .http .util .EntityUtils ;
11
13
import org .testng .annotations .AfterClass ;
12
14
import org .testng .annotations .AfterMethod ;
13
15
import org .testng .annotations .BeforeClass ;
@@ -176,18 +178,22 @@ private static Object[][] provideAuthUserPasswordTestData() {
176
178
};
177
179
}
178
180
179
- private static WireMockServer newServer () {
181
+ private static WireMockServer newServer (int delayMillis ) {
180
182
WireMockServer server = new WireMockServer (
181
183
WireMockConfiguration .wireMockConfig ().dynamicPort ());
182
184
server .start ();
183
185
server .stubFor (WireMock .post (WireMock .urlPathMatching ("/*" ))
184
186
.willReturn (WireMock .aResponse ().withStatus (200 ).withHeader ("Connection" , "Keep-Alive" )
185
187
.withHeader ("Content-Type" , "text/plain; charset=UTF-8" )
186
188
.withHeader ("Transfer-Encoding" , "chunked" ).withHeader ("Keep-Alive" , "timeout=3" )
187
- .withBody ("OK........................." ).withFixedDelay (2 )));
189
+ .withBody ("OK........................." ).withFixedDelay (delayMillis )));
188
190
return server ;
189
191
}
190
192
193
+ private static WireMockServer newServer () {
194
+ return newServer (2 );
195
+ }
196
+
191
197
private static void shutDownServerWithDelay (final WireMockServer server , final long delayMs ) {
192
198
new Thread () {
193
199
public void run () {
@@ -203,38 +209,104 @@ public void run() {
203
209
}.start ();
204
210
}
205
211
206
- // @Test(groups = "unit", dependsOnMethods = { "testWithRetry" }, expectedExceptions = { NoHttpResponseException.class })
207
- public void testWithoutRetry () throws Exception {
208
- final WireMockServer server = newServer ();
212
+ @ Test (expectedExceptions = { NoHttpResponseException .class })
213
+ public void testReproduceFailedToResponseProblem () throws Exception {
214
+ final WireMockServer server = newServer (2 );
209
215
210
216
ClickHouseProperties props = new ClickHouseProperties ();
217
+ // Disable retry when "failed to respond" occurs.
211
218
props .setMaxRetries (0 );
219
+ // Disable validation to reproduce "failed to respond" problem
220
+ props .setValidateAfterInactivityMillis (0 );
221
+ // Ensure there is exactly one TCP connection in connection pool and therefore be re-used between
222
+ // multiple http requests.
223
+ props .setMaxTotal (1 );
224
+ props .setDefaultMaxPerRoute (1 );
225
+
212
226
ClickHouseHttpClientBuilder builder = new ClickHouseHttpClientBuilder (props );
213
227
CloseableHttpClient client = builder .buildClient ();
214
228
HttpPost post = new HttpPost ("http://localhost:" + server .port () + "/?db=system&query=select%201" );
215
229
216
- shutDownServerWithDelay (server , 500 );
230
+ try {
231
+ // Make the 1st http request to establish one tcp connection and keep it in the pool.
232
+ {
233
+ HttpResponse response = client .execute (post );
234
+ EntityUtils .consume (response .getEntity ());
235
+ }
236
+
237
+ // Close the server, now the pooling tcp connection is half closed.
238
+ server .shutdownServer ();
239
+ server .stop ();
240
+
241
+ // The 2nd http request will re-use the pooling tcp connection which is stale
242
+ // and "failed to respond" occurs.
243
+ {
244
+ HttpResponse response = client .execute (post );
245
+ EntityUtils .consume (response .getEntity ());
246
+ }
247
+ } finally {
248
+ client .close ();
249
+ }
250
+ }
251
+
252
+ @ Test (expectedExceptions = { HttpHostConnectException .class })
253
+ public void testEnableValidation () throws Exception {
254
+ final WireMockServer server = newServer (2 );
255
+
256
+ ClickHouseProperties props = new ClickHouseProperties ();
257
+ // Disable retry when "failed to respond" occurs.
258
+ props .setMaxRetries (0 );
259
+ // Disable validation to reproduce "failed to respond" problem
260
+ props .setValidateAfterInactivityMillis (1 );
261
+ // Ensure there is exactly one TCP connection in connection pool and therefore be re-used between
262
+ // multiple http requests.
263
+ props .setMaxTotal (1 );
264
+ props .setDefaultMaxPerRoute (1 );
265
+
266
+ ClickHouseHttpClientBuilder builder = new ClickHouseHttpClientBuilder (props );
267
+ CloseableHttpClient client = builder .buildClient ();
268
+ HttpPost post = new HttpPost ("http://localhost:" + server .port () + "/?db=system&query=select%201" );
217
269
218
270
try {
219
- client .execute (post );
271
+ // Make the 1st http request to establish one tcp connection and keep it in the pool.
272
+ {
273
+ HttpResponse response = client .execute (post );
274
+ EntityUtils .consume (response .getEntity ());
275
+ }
276
+
277
+ // Sleep a while to wait for the validation reaches inactivity timeout.
278
+ Thread .sleep (5 );
279
+
280
+ // Close the server, now the pooling tcp connection is half closed.
281
+ server .shutdownServer ();
282
+ server .stop ();
283
+
284
+ // The 2nd http request re-uses the pooling tcp connection.
285
+ // But the validation checks that the connection has been stale, thus a
286
+ // new tcp connection is attempted to establish to the closed server
287
+ // which leads to HttpHostConnectException.
288
+ {
289
+ HttpResponse response = client .execute (post );
290
+ EntityUtils .consume (response .getEntity ());
291
+ }
220
292
} finally {
221
293
client .close ();
222
294
}
223
295
}
224
296
225
- // @Test(groups = "unit", expectedExceptions = { HttpHostConnectException.class })
297
+ @ Test (expectedExceptions = { HttpHostConnectException .class })
226
298
public void testWithRetry () throws Exception {
227
- final WireMockServer server = newServer ();
299
+ final WireMockServer server = newServer (500 );
228
300
229
301
ClickHouseProperties props = new ClickHouseProperties ();
230
- // props.setMaxRetries(3);
302
+ props .setMaxRetries (3 );
231
303
ClickHouseHttpClientBuilder builder = new ClickHouseHttpClientBuilder (props );
232
304
CloseableHttpClient client = builder .buildClient ();
233
305
HttpContext context = new BasicHttpContext ();
234
306
context .setAttribute ("is_idempotent" , Boolean .TRUE );
235
307
HttpPost post = new HttpPost ("http://localhost:" + server .port () + "/?db=system&query=select%202" );
236
-
237
- shutDownServerWithDelay (server , 500 );
308
+
309
+ shutDownServerWithDelay (server , 100 );
238
310
239
311
try {
240
312
client .execute (post , context );
0 commit comments