Как предотвратить зависания на SocketInputStream.socketRead0 на Java?

выполнение миллионов HTTP-запросов с различными библиотеками Java дает мне потоки, зависшие на:

java.net.SocketInputStream.socketRead0()

что это .

Я попытался настроить Http-клиент Apche и RequestConfig иметь тайм-ауты на (Я надеюсь) все, что возможно, но все же,у меня (вероятно, бесконечный) висит на socketRead0. Как от них избавиться?

Повиснутый коэффициент около ~1 в 10000 запросов (до 10000 различных хозяев) и это может длиться, вероятно, навсегда (я подтвердил, что нить висела как все еще действительная после 10 часов).

JDK 1.8 В Windows 7.

мой HttpClient фабрика:

SocketConfig socketConfig = SocketConfig.custom()
            .setSoKeepAlive(false)
            .setSoLinger(1)
            .setSoReuseAddress(true)
            .setSoTimeout(5000)
            .setTcpNoDelay(true).build();

    HttpClientBuilder builder = HttpClientBuilder.create();
    builder.disableAutomaticRetries();
    builder.disableContentCompression();
    builder.disableCookieManagement();
    builder.disableRedirectHandling();
    builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());
    builder.setDefaultSocketConfig(socketConfig);

    return HttpClientBuilder.create().build();

мой RequestConfig фабрика:

    HttpGet request = new HttpGet(url);

    RequestConfig config = RequestConfig.custom()
            .setCircularRedirectsAllowed(false)
            .setConnectionRequestTimeout(8000)
            .setConnectTimeout(4000)
            .setMaxRedirects(1)
            .setRedirectsEnabled(true)
            .setSocketTimeout(5000)
            .setStaleConnectionCheckEnabled(true).build();
    request.setConfig(config);

    return new HttpGet(url);

OpenJDK socketRead0 источник

Примечание: На самом деле у меня есть какой - то "трюк" - я могу запланировать .getConnectionManager().shutdown() в другой Thread С отменой Future если запрос закончен правильно, но он нецелесообразен, а также убивает весь HttpClient не только один запрос.

7 ответов


хотя в этом вопросе упоминается Windows, у меня такая же проблема в Linux. Похоже, есть недостаток в том, как JVM реализует блокировку таймаутов сокета:

подводя итог, тайм-аут для блокирующих сокетов реализуется путем вызова poll в Linux (и select в Windows), чтобы определить, что данные доступны перед вызовом recv. Однако, по крайней мере, в Linux оба метода могут указывать на то, что данные доступны, когда их нет, что приводит к recv блокирование на неопределенный срок.

из опроса (2) раздел ошибок man-страницы:

см. обсуждение ложных уведомлений о готовности в разделе ошибок select(2).

из select (2) раздел ошибок man page:

в Linux выберите () может сообщить дескриптор файла сокета как " ready для чтения", хотя тем не менее последующие блоки чтения. Это может например, когда поступили данные, но после их изучения неправильная контрольная сумма и отбрасывается. Могут быть и другие обстоятельства в котором файловый дескриптор нечестно сообщается как готовый. Таким образом может быть безопаснее использовать O_NONBLOCK для сокетов, которые не должны блокировать.

код клиента HTTP Apache немного трудно следовать, но это появляется этот срок действия соединения установлен только для HTTP keep-alive connections (которые вы отключили) и является неопределенным, если сервер не указывает иное. Поэтому, как указал Олег, политика выселения соединения подход не будет работать в вашем случае и не может быть положен в целом.


As Клинт сказал, вы должны рассмотреть неблокирующий HTTP-клиент или (видя, что вы используете Apache Httpclient) реализовать многопоточное выполнение запроса чтобы предотвратить возможные зависания основного потока приложения (это не решит проблему, но лучше, чем перезагрузить приложение, потому что зависает). Во всяком случае, вы установили setStaleConnectionCheckEnabled свойство, но проверка устаревшего соединения не является 100% надежной, из учебника Apache Httpclient:

одним из основных недостатков классической модели ввода-вывода блокировки является что сетевой сокет может реагировать на события ввода-вывода только при блокировке операция ввода-вывода. Когда соединение освобождается обратно к менеджеру, его можно держать живым однако он неспособен контролировать состояние сокет и реагировать на любые события ввода-вывода. Если соединение будет закрыто серверная сторона, соединение на стороне клиента не может обнаружить изменение состояния соединения (и соответствующая реакция закрытие разъем на конце).

HttpClient пытается смягчить проблему, проверяя, соединение "устаревшее", которое больше не действует, потому что оно было закрыто на стороне сервера перед использованием соединения для выполнения запрос http. Проверка устаревшего соединения не является 100% надежной и добавляет 10 до 30 MS накладных расходов на каждое выполнение запроса.

команда Apache HttpComponents рекомендует реализацию политика выселения соединения

единственное возможное решение, которое не включает один поток на модель сокета для холостых соединений-это выделенный поток монитора для выселения соединений, которые считаются просроченными из-за длительного периода бездействия. Поток монитора может периодически вызывать ClientConnectionManager#closeExpiredConnections () метод, чтобы закрыть все истекшие подключения и вытеснение закрытых подключений из пула. Он мочь также необязательно вызывать ClientConnectionManager#closeIdleConnections() способ закрытия всех соединений, бездействовавших над заданным период времени.

взгляните на пример кода политика выселения соединения раздел и попробуйте реализовать его в своем приложении вместе с выполнением многопоточного запроса, я думаю, что реализация обоих механизмов предотвратит ваши нежелательные зависания.


У меня есть более 50 машин, которые делают около 200k запросов/день/машина. Они работают под управлением Amazon Linux AMI 2017.03. Я раньше jdk1.8.0_102, теперь у меня jdk1.8.0_131. Я использую как apacheHttpClient, так и OKHttp в качестве библиотек очистки.

на каждой машине было запущено 50 потоков, и иногда потоки теряются. После профилирования с помощью youkit java profiler я получил

ScraperThread42 State: RUNNABLE CPU usage on sample: 0ms
java.net.SocketInputStream.socketRead0(FileDescriptor, byte[], int, int, int) SocketInputStream.java (native)
java.net.SocketInputStream.socketRead(FileDescriptor, byte[], int, int, int) SocketInputStream.java:116
java.net.SocketInputStream.read(byte[], int, int, int) SocketInputStream.java:171
java.net.SocketInputStream.read(byte[], int, int) SocketInputStream.java:141
okio.Okio.read(Buffer, long) Okio.java:139
okio.AsyncTimeout.read(Buffer, long) AsyncTimeout.java:211
okio.RealBufferedSource.indexOf(byte, long) RealBufferedSource.java:306
okio.RealBufferedSource.indexOf(byte) RealBufferedSource.java:300
okio.RealBufferedSource.readUtf8LineStrict() RealBufferedSource.java:196
okhttp3.internal.http1.Http1Codec.readResponse() Http1Codec.java:191
okhttp3.internal.connection.RealConnection.createTunnel(int, int, Request, HttpUrl) RealConnection.java:303
okhttp3.internal.connection.RealConnection.buildTunneledConnection(int, int, int, ConnectionSpecSelector) RealConnection.java:156
okhttp3.internal.connection.RealConnection.connect(int, int, int, List, boolean) RealConnection.java:112
okhttp3.internal.connection.StreamAllocation.findConnection(int, int, int, boolean) StreamAllocation.java:193
okhttp3.internal.connection.StreamAllocation.findHealthyConnection(int, int, int, boolean, boolean) StreamAllocation.java:129
okhttp3.internal.connection.StreamAllocation.newStream(OkHttpClient, boolean) StreamAllocation.java:98
okhttp3.internal.connection.ConnectInterceptor.intercept(Interceptor$Chain) ConnectInterceptor.java:42
okhttp3.internal.http.RealInterceptorChain.proceed(Request, StreamAllocation, HttpCodec, Connection) RealInterceptorChain.java:92
okhttp3.internal.http.RealInterceptorChain.proceed(Request) RealInterceptorChain.java:67
okhttp3.internal.http.BridgeInterceptor.intercept(Interceptor$Chain) BridgeInterceptor.java:93
okhttp3.internal.http.RealInterceptorChain.proceed(Request, StreamAllocation, HttpCodec, Connection) RealInterceptorChain.java:92
okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(Interceptor$Chain) RetryAndFollowUpInterceptor.java:124
okhttp3.internal.http.RealInterceptorChain.proceed(Request, StreamAllocation, HttpCodec, Connection) RealInterceptorChain.java:92
okhttp3.internal.http.RealInterceptorChain.proceed(Request) RealInterceptorChain.java:67
okhttp3.RealCall.getResponseWithInterceptorChain() RealCall.java:198
okhttp3.RealCall.execute() RealCall.java:83

я узнал, что у них есть исправление для этого

https://bugs.openjdk.java.net/browse/JDK-8172578

в JDK 8u152 (ранний доступ). Я установил его на одну из наших машин. Теперь я жду хороших результатов.


вы должны рассмотреть неблокирующий HTTP-клиент, такой как Гризли или Нетти которые не имеют операций блокировки для зависания потока.


для Apache HTTP Client (блокировка) я нашел лучшее решение-getConnectionManager (). и выключи его.

поэтому в решении с высокой надежностью я просто планирую завершение работы в другом потоке, и в случае, если запрос не будет завершен, я отключаюсь от другого потока


я столкнулся с той же проблемой, используя общий http-клиент apache.

существует довольно простой обходной путь (который не требует отключения диспетчера соединений):

нам нужно выполнить запрос от вопроса в новом потоке с некоторыми дополнительными расходами:

  • выполнить запрос в отдельном потоке, закрыть запрос и отпустить его соединение в другом потоке, прервать подвесной поток
  • не работать EntityUtils.consumeQuietly(response.getEntity()) в блоке finally (потому что он висит на "Мертвом" соединении)

Сначала добавьте интерфейс

interface RequestDisposer {
    void dispose();
}

выполнение http-запросов в новом потоке

final AtomicReference<RequestDisposer> requestDisposer = new AtomicReference<>(null);  

final Thread thread = new Thread(() -> {
    final HttpGet request = new HttpGet("http://my.url");
    final RequestDisposer disposer = () -> {
        request.abort();
        request.releaseConnection();
    };
    requestDiposer.set(disposer);

    try (final CloseableHttpResponse response = httpClient.execute(request))) {
        ...
    } finally {
      disposer.dispose();
    } 
};)
thread.start()

вызов dispose() в основной резьбе, чтобы закрыть подвесное соединение

requestDisposer.get().dispose(); // better check if it's not null first
thread.interrupt();
thread.join();

это исправило проблему для меня.

мой stacktrace выглядел так:

java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:139)
at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:155)
at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:284)
at org.apache.http.impl.io.ChunkedInputStream.getChunkSize(ChunkedInputStream.java:253)
at org.apache.http.impl.io.ChunkedInputStream.nextChunk(ChunkedInputStream.java:227)
at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:186)
at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:137)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)

кому это может быть интересно, он легко воспроизводит, прерывает поток без прерывания запроса и освобождения соединения (соотношение примерно 1/100). Windows 10, версия 10.0. jdk8.151-x64.


учитывая, что никто не ответил До сих пор, вот мой take

ваша настройка тайм-аута выглядит совершенно нормально для меня. Причина, по которой некоторые запросы, как представляется, постоянно блокируются в java.net.SocketInputStream#socketRead0() вызов, вероятно, будет из-за комбинации неправильных серверов и вашей локальной конфигурации. Тайм-аут сокета определяет максимальный период бездействия между двумя последовательными операциями чтения ввода-вывода (или, другими словами, двумя последовательными входящими пакетами). Сокет таймаут составляет 5000 миллисекунды. Пока противоположная конечная точка продолжает отправлять пакет каждые 4,999 миллисекунды для сообщения с кодировкой фрагмента, запрос никогда не будет тайм-аут и в конечном итоге отправит большую часть своего времени, заблокированного в java.net.SocketInputStream#socketRead0(). Вы можете узнать, так ли это или нет, запустив HttpClient с включенным журналированием проводов.