Прошу совета по Jetty HttpClient Hang

У меня есть небольшое приложение, которое просто опрашивает сервер с помощью Jetty v9.2, HttpClient. Через несколько дней приложение зависнет. Первоначально мы определили, что пул потоков должен быть увеличен в размере, чтобы уменьшить падение производительности. Это изменение восстановило производительность в течение нескольких дней. Блокада осталась. Причина была изолирована от вызовов HTTP GET (проблема исчезает, если мы закомментируем метод).

Основная причина, которая лежит в основе Jetty HttpClient Управление соединениями или Управление потоками. Обычно Jetty HttpClient создает набор потоков для обработки HTTP GET (см. ниже), эти потоки возникают и исчезают, как и следовало ожидать. Примерно через 40 часов работы JDK VisualVM показывает как минимум 9 потоков подключения, которые не не исчезают немедленно:

  • HttpClient - планировщик x 1
  • HttpClient — клиент-селектор SectorManager x 4
  • HTTP-клиент x 4

также

  • RMI TCP-соединение

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

Иногда мы видим, что эти «зависшие» потоки задерживаются до часа, прежде чем они исчезают из отображения потока VisualVM. По крайней мере, через 36 часов мы видим, что потоки остаются, и мы не видим, чтобы они исчезли.

Через несколько дней программа зависает. Указанное объяснение — утечка экземпляров потока, которые не были очищены. появляется приложение. заканчивается потоки и не может больше работать. Это, безусловно, останавливает HTTP GET, о чем свидетельствуют серверные журналы.

Основной HTTP-вызов использует приведенный ниже код, метод HttpClient GET:

 /**
  *   GET
  *   @return null or string returned from server
  **/
 public static String get( final String command ){

    String          rslt        = null;
    final String    reqStr      = "http://www.google.com";  //  (any url)

    HttpClient      httpClient  = new HttpClient();
    Request         request;
    ContentResponse response;

    try {
            //-- Start HttpClient
        httpClient.start();

        request   = httpClient.newRequest( reqStr );

        response  = request.send();

        if( null == response ){
            LOG.error( "NULL returned from previous HTTP request.");
        }
        else {
            if( (501 == response.getStatus()) || (502 == response.getStatus()) ){
                setNetworkUnavailable(String.format("HTTP Server error: %d", response.getStatus() ));
            }
            else {
                if(  404 == response.getStatus() ){
                    Util.puts(LOG,"HTTP Server error: 404");
    //              ignore message since we are talking to an old server
                }
                else if( 200 == response.getStatus() ){
                    rslt = response.getContentAsString();
                }
                else {
                    LOG.error(String.format( "    * Response status: \"%03d\".", response.getStatus() ));
                }
                setNetworkAvailable();
            }
        }
    }
    catch ( InterruptedException iEx ){
        LOG.warn( "InterruptException processing: "+reqStr, iEx );
    }
    catch ( Exception ex ){

        Throwable cause = eEx.getCause();
        if( (cause instanceof NoRouteToHostException) ||
            (cause instanceof EOFException)           ||
            (cause instanceof SocketException)
                && cause.getMessage().startsWith( EX_NETWORK_UNREACHABLE ) ){

            setNetworkUnavailable( cause.getMessage() );
        }
        else {
            LOG.error( "Exception on: "+command, ex );
        }
    }
    finally {
        try {
            httpClient.stop();
        }
        catch ( Exception ex ){
            LOG.error( "Exception httpClient.stop(), ServerManager::get()", ex );
        }
    }

    return rslt;

}//get method

Это основано на простых примерах, мало подробностей об использовании HttpClient. Все ли сделано по Хойлу?

При разных запусках мы также видим следующие исключения и сообщения журнала:

  • [36822522] WARN 2014-Sep-02 02:46:28.464> HttpClient@2116772232{СТОПИНГ,8‹=0‹=200,i=0,q=0} Не удалось остановить поток[HttpClient@2116772232-729770,5 ,]

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

  • java.util.concurrent.TimeoutException (ExecutionException)

Похоже, это исключение тайм-аута потока. Какая нить хоть? Относится ли это к потокам HTTP-соединений? Я думаю, что как минимум, когда службы обнаруживают ошибки внутри, они могут, по крайней мере, указать местоположение ошибки и трассировку стека.

Есть очевидные вопросы:

  1. Написан ли код метода get() так, как требуется, чтобы не было утечек или не оставалось зависших ресурсов для кода Jetty HttpClient?
  2. How can we catch the warning: "Couldn't stop Thread" error?
    • What is the impact of this error? Is there a way to 'smash' a thread stuck like that?
    • Это как-то связано с 10 висящими нитями подключения? Есть только одно предупреждающее сообщение.
    • Можно представить, что зависшая нить требует метки ERROR, а не предупреждения.
  3. Существует ли процесс обнаружения ошибок потоков и ошибок в целом в Jetty HttpClient?
  4. What attributes are available for the HttpClient to tune the service?
    • Are there settings we can use to directly influence the thread-locking?
  5. Какие атрибуты доступны в среде или контексте HttpClient для управления настройкой службы?
  6. Can the Jetty HttpClient be restarted / rebooted or just stopped?
    • Jetty calls are only made in the GET method shown (albeit with more logging, etc.)
  7. Является ли поток RMI частью вызовов Jetty HttpClient?

Еще одно наблюдение заключается в том, что когда мы «застреваем» потоки в VisualVM, он показывает избыточные потоки демона на панели «Потоки», а не увеличение количества потоков, не являющихся демонами.

Запуск кода, показанного выше, в цикле for в течение примерно 3 или 4 часов с 250-миллисекундным перерывом между вызовами HttpClient send() показывает утечку потока — ее легко воспроизвести в Linux. Выходные данные журнала не показывают предупреждений и только две ошибки тайм-аута в сети на расстоянии не менее 30 минут от утечки потока.

Предложения, наблюдения, улучшения и ответы приветствуются. Заранее благодарим.

Похожие вопросы:

Эти вопросы охватывают некоторые очень похожие моменты


person will    schedule 02.09.2014    source источник


Ответы (1)


Эта ситуация кажется разрешенной путем обеспечения двух вещей.

  1. Обеспечение достаточного количества потоков в пуле потоков приложения.
  2. Убедитесь, что код, использующий Jetty, очищает и перехватывает/управляет всеми исключениями.

Оба действия взаимосвязаны. Если иногда HttpClient пропускает исключение или ошибку, поток зависает. Кажется, единственный способ избежать этого — убедиться, что каждый используемый HttpCLient вызывает HttpCLient.stop(). Это должно быть включено в предложение finally {...}.

Во-вторых, асинхронные вызовы должны ждать CompleteListener перед вызовом HttpCLient.stop(). Кажется, это единственный способ гарантировать, что остановка была сделана «чисто». В некоторых случаях вызовы stop() проходят успешно. В конце концов, некоторые из них вызовут исключения, и ваше приложение будет медленно терять ресурсы. Внешний вид выглядит так, как будто JVM зависла, но некоторые задачи, не связанные с демоном, могут продолжаться (например, поток GUI), и вы можете не заметить проблему, пока на самом ПК не закончатся ресурсы или произойдет сбой. Это крайний случай, когда ## рубрика ## работает в течение нескольких недель.

Здесь показан надежный пример правильного закрытия HttpClient:

Количество потоков будет зависеть от вашего приложения. Я предлагаю использовать jVisualVM или что-то подобное, чтобы ваш Jetty потоки сначала очищаются должным образом, прежде чем настраивать количество потоков в вашем пуле потоков.

Я чувствую, что документация должна подчеркивать очистку и обеспечение вызова stop(). Насколько я могу судить, информация о том, как завершить асинхронный вызов, недокументирована. Пока ваши вызовы Jetty останавливаются корректно, предоставление достаточного количества потоков, по-видимому, решает эту проблему -- с обычными предостережениями для управления параллелизмом.

person will    schedule 29.10.2014