Подтвердить что ты не робот

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

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

java.net.SocketInputStream.socketRead0()

Какая функция native.

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

Отношение Хонга составляет около ~ 1 на 10000 запросов (до 10000 разных хостов), и оно может длиться, вероятно, навсегда (я подтвердил, что поток по-прежнему действует после 10 часов).

JDK 1.8 на Windows 7.

Мой HttpClient factory:

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 factory:

    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, не только этот единственный запрос.

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Из опроса (2) man page Раздел ошибок:

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

Из раздела (2) man страницы BUGS:

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

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

Ответ 3

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

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

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

Команда Apache HttpComponents рекомендует внедрить Политика выключения соединения

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

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

Ответ 4

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

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

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$2.read(Buffer, long) Okio.java:139
okio.AsyncTimeout$2.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 (ранний доступ). Я установил его на одну из наших машин. Теперь я жду, чтобы увидеть хорошие результаты.

Ответ 5

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

Ответ 6

Учитывая, что никто еще не ответил, вот мой прием

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

Ответ 7

Я столкнулся с той же проблемой, используя общий 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 not null first
thread.interrupt();
thread.join();

Это решило проблему для меня.

Моя трассировка стека выглядела так:

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.