Приведут ли обратные вызовы метода к цепочке к переполнению стека в Java? - PullRequest
0 голосов
/ 23 февраля 2019

Мое приложение обрабатывает много транзакций HTTP-запросов / ответов, где ответ от одной службы ведет к следующему шагу и последующему запросу и т. Д. По последовательности шагов.

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

private void runFirstStep() {
    String firstRequest = buildRequest();
    sendHttpRequest(firstRequest, this::handleFirstStepResponse);
}

private void handleFirstStepResponse(InputStream responseBody) {
    doStuffWithFirstStepResponse(responseBody);
    String secondRequest = buildSecondRequest();
    sendHttpRequest(secondRequest, this::handleSecondStepResponse);
}

private void handleSecondStepResponse(InputStream responseBody) {
    doStuffWithSecondStepResponse(responseBody);
    String thirdRequest = buildThirdRequest();
    sendHttpRequest(thirdRequest, this::handleThirdStepResponse);
}

private void handleThirdStepResponse(InputStream responseBody) {
    doStuffWithThirdStepResponse(responseBody);
    // The flow has finished, so no further HTTP transactions.
}

Хотя в моем случае длина последовательности в настоящее время достигает около 26 шагов, все соединены таким образом.

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

Так что вопросы таковы:

  1. Будет ли последовательностьнапример, из нескольких десятков связанных цепочек существует риск переполнения стека, или требуется намного больше злоупотреблений, чем это, чтобы исчерпать типичный стек?Обратите внимание, что моя упрощенная структура кода выше скрывает тот факт, что методы на самом деле делают довольно много (сборка XML, извлечение XML, запись данных запроса / ответа в файл журнала), поэтому мы не говорим об упрощенных задачах.

  2. Есть ли другой шаблон, который я должен использовать, который не оставит цепочку методов, терпеливо ожидающих завершения всего потока?Мой метод sendHttpRequest уже использует инфраструктуру HTTP JDK 11 для генерации CompletableFuture<HttpResponse<InputStream>>, но мой метод sendHttpRequest затем просто ожидает его завершения и вызывает указанный метод обратного вызова с результатом.Нужно ли создавать новый поток вместо этого для обработки CompletableFuture, чтобы вызывающий метод мог корректно закрыться?И как сделать это, не вызывая JVM для выключения (если рассматривать медленный HTTP-ответ, то в результате JVM не будет выполнять методы)?

переполнение стека (сайт,не исключение) Я, конечно, ищу ответы, которые касаются механики голого металла Java, а не домыслов или анекдотов.

Обновление : просто чтобы уточнить, мой метод sendHttpRequestв настоящее время имеет такую ​​форму:

private void sendHttpRequest(String request,
        Consumer<InputStream> callback) {
    HttpRequest httpRequest = buildHttpRequestFromXml(request);
    CompletableFuture<HttpResponse<InputStream>> completableExchange
            = httpClient.
            sendAsync(httpRequest, BodyHandlers.ofInputStream());
    HttpResponse<InputStream> httpResponse = completableExchange.join();
    InputStream responseBody = getBodyFromResponse(httpResponse);
    callback.accept(responseBody);
}

Важным моментом является то, что метод Java HttpClient.sendAsync возвращает CompletablFuture, а затем для этого объекта вызывается join(), чтобы дождаться ответа HTTP.принимается и возвращается как объект HttpResponse, который затем используется для передачи тела ответа указанному методу обратного вызова.Но мой вопрос связан не с последовательностями запросов / ответов HTTP, а с рисками и рекомендациями при работе с потоками любого типа, которые поддаются структуре ожидания результатов и обратного вызова.

1 Ответ

0 голосов
/ 24 февраля 2019

Прежде всего, если метод принимает обратный вызов в качестве параметра, он не должен блокировать вызывающий поток.Если вы блокируете, нет необходимости в обратном вызове. (Вы можете просто вернуть InputStream из sendHttpRequest и вызвать следующий метод с ним.)

Вы должны выполнить полную асинхронность с использованием CompletableFuture's.Но есть одна вещь, которую вы должны рассмотреть здесь.Параллельные потоковые операции и CompletableFuture используют общий пул, когда они специально не выполняются в Executor (пул потоков).Поскольку загрузка http является блокирующей операцией, вам не следует выполнять ее в общем пуле (чтобы не блокировать потоки общего пула, выполняющие операции ввода-вывода).Вы должны создать пул ввода-вывода и передать его методам CompletableFuture, которые принимают Executor в качестве параметра при загрузке.

Что произойдет, если вы продолжите работу с текущим проектом;

Когда методПосле вызова будет создан кадр стека и помещен в стек вызывающего потока.Этот фрейм будет содержать адрес возврата того места, где этот метод был вызван, параметры этого метода и локальные переменные метода.Если эти параметры и переменные являются примитивными типами, они будут храниться в стеке, если они являются объектами, их адреса будут храниться в стеке.И когда этот метод завершает выполнение, его кадр будет уничтожен.

Цепочка из 26 вызовов методов не должна быть проблемой для переполнения стека.Также Вы можете контролировать размер стека с помощью ключа -Xss.Размер стека по умолчанию будет разным для каждой платформы (32- и 64-битные также влияют на размер по умолчанию.) Если вы предоставляете исполняемую команду для своего приложения, вы можете определить это значение, если у вас есть опасения по этому поводу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...