У меня есть проект, в котором я загружаю много страниц одновременно во многих задачах, которые обрабатываются через ThreadPool
(size = 200). Все эти задачи используют один и тот же метод getPage
для загрузки страницы (с Apache Commons HttpClient и Apache Commons IO):Застревание в SocketInputStream.socketRead0
public static String getPage(String url)
throws IOException {
HttpUriRequest request = new HttpGet(url);
HttpResponse response = HTTP_CLIENT_BUILDER.build().execute(request);
try (InputStream content = response.getEntity().getContent()) {
return IOUtils.toString(content, "UTF-8");
}
}
в то время как HTTP_CLIENT_BUILDER
статическое поле инициализируется следующим образом:
private static final HttpClientBuilder HTTP_CLIENT_BUILDER = HttpClients.custom()
.setDefaultRequestConfig(RequestConfig.custom()
.setSocketTimeout(SOCKET_TIMEOUT_MS) // 60_000
.setConnectTimeout(CONNECTION_TIMEOUT_MS) // 5_000
.build());
проблемы оператор: в какой-то момент (когда большая часть задач завершена) все оставшиеся потоки застревают по собственному методу SocketInputStream.socketRead0
, поэтому jdb
говорит, что все они работают (хм, да, я ожидаю, что поведение с родным встретил корыто работает :-)):
> threads
Group system:
(java.lang.ref.Reference$ReferenceHandler)0xac4 Reference Handler cond. waiting
(java.lang.ref.Finalizer$FinalizerThread)0xac5 Finalizer cond. waiting
(java.lang.Thread)0xac6 Signal Dispatcher running
(java.lang.Thread)0xac7 Java2D Disposer cond. waiting
Group main:
(java.lang.Thread)0xac9 pool-1-thread-5 running
(java.lang.Thread)0xaca pool-1-thread-12 running
(... 12 more threads from ThreadPool ...)
(java.lang.Thread)0xad7 DestroyJavaVM running
> where 0xac9
[1] java.net.SocketInputStream.socketRead0 (native method)
[2] java.net.SocketInputStream.read (SocketInputStream.java:150)
[3] java.net.SocketInputStream.read (SocketInputStream.java:121)
[4] sun.security.ssl.InputRecord.readFully (InputRecord.java:465)
[5] sun.security.ssl.InputRecord.read (InputRecord.java:503)
[6] sun.security.ssl.SSLSocketImpl.readRecord (SSLSocketImpl.java:961)
[7] sun.security.ssl.SSLSocketImpl.performInitialHandshake (SSLSocketImpl.java:1,363)
[8] sun.security.ssl.SSLSocketImpl.startHandshake (SSLSocketImpl.java:1,391)
[9] sun.security.ssl.SSLSocketImpl.startHandshake (SSLSocketImpl.java:1,375)
[10] org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket (SSLConnectionSocketFactory.java:275)
[11] org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket (SSLConnectionSocketFactory.java:254)
[12] org.apache.http.impl.conn.HttpClientConnectionOperator.connect (HttpClientConnectionOperator.java:117)
[13] org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect (PoolingHttpClientConnectionManager.java:314)
[14] org.apache.http.impl.execchain.MainClientExec.establishRoute (MainClientExec.java:363)
[15] org.apache.http.impl.execchain.MainClientExec.execute (MainClientExec.java:219)
[16] org.apache.http.impl.execchain.ProtocolExec.execute (ProtocolExec.java:195)
[17] org.apache.http.impl.execchain.RetryExec.execute (RetryExec.java:86)
[18] org.apache.http.impl.execchain.RedirectExec.execute (RedirectExec.java:108)
[19] org.apache.http.impl.client.InternalHttpClient.doExecute (InternalHttpClient.java:186)
[20] org.apache.http.impl.client.CloseableHttpClient.execute (CloseableHttpClient.java:82)
[21] org.apache.http.impl.client.CloseableHttpClient.execute (CloseableHttpClient.java:106)
[22] <package>.Utils.getPage (Utils.java:122)
[23...] <internal details>
> # the same picture for all of them
Я не понимаю, почему это может произойти, но я нашел Java bug, который, возможно, связанные с этим вопросом. Так что, возможно, я не ищу реального решения, но для некоторого обходного пути.
Поскольку ошибка подается против Linux, я должен сказать, что я также использую виртуальную машину под управлением Ubuntu 14.04 x86_64
UPD: Хорошо, что я пытался теперь добавлять новый тайм-аут с setConnectionRequestTimeout
(просто чтобы убедиться, что не работает) добавить finally
блок жгутов getPage
:
...
try (InputStream content = response.getEntity().getContent()) {
return IOUtils.toString(content, "UTF-8");
} finally {
httpClient.getConnectionManager().closeIdleConnections(0, TimeUnit.NANOSECONDS);
}
Давайте посмотрим, если это поможет.
UPD2: похоже, это немного помогает, но все же у меня есть проблемы с постоянным запуском, которые возникают примерно один раз в день.
Thread, который делает 'Socket.read' будет показано вверх как' Runnable' см это SO сообщение: http://stackoverflow.com/questions/12544212. Скорее всего, удаленная сторона закрывает конец разъема, поэтому ваши задачи не могут быть завершены. Например, вы отправили больше задач Исполнителю, чем необходимо для загрузки удаленных ресурсов, а оставшаяся часть задач оставлена в ожидании. –
@VictorSorokin, который не должен происходить, поскольку я установил таймауты (см. Инициализатор для 'HTTP_CLIENT_BUILDER') –
Да, забыл, извините. Затем я рассмотрю соединение с tcpdump или аналогичным, чтобы понять, что поддерживает TCP-соединение. Возможно, серверные журналы также могут быть полезны. –