Обучение и трудоустройство Java-программистов от Junior до Senior

Thread.sleep()

 
 
 
Сообщения:429
Коллеги, призадумался о том, как лучше организовывать выполнение такого сценария:
* меня кто-то вызывает (синхронно). ну например, soap или rest - не суть
* я периодически выполняю проверку некого третьего ресурса (по не получу удовлетворительный ответ или до истечения некоторого времени)
* возвращаю либо успешный ответ, либо кидаю ошибку о таймауте
Первое, что пришло на ум - лопатать в цикле, с периодическим "усыплением" потока.
Но тогда получается, что мы держим поток входного запроса (а по сути спим).
Для таких тривиальных задач должно же быть более умное решение?
 
 
Сообщения:2391
Если soap или rest действительно не важно, оба цоба сейчас имеют нормальную поддержку асинхронности, то природа API вашего третьестороннего ресурса от которого что-то ожидается, имеет значение, если с ресурсом нельзя работать в ассинхроном стиле(например это RDBMS а jdbc не умеет ассинхроность), то особого смысла пытаться экономить тред нету, у вас все равно какой-то тред будет синхронно ждать ответа.
 
 
Сообщения:429
Третья сторона, да, это jdbc
Только вот, пардон, не понял почему soap и rest асинхронны? Мне как-то довольно уютно жилось во вселенной, где они были синхронными, а уже всякие там jms - асинхронны.
 
 
Сообщения:2391
И JAXRS, и JAXWS имеют возможность реализовать эндпойнт в ассинхронном стиле уже на уровне спецификации, что в общем случае позволяет Вам заиспользовать скажем CompletableFuture и не блокировать тред контейнера в ожидании результата, но это имеет смысл только до той поры, пока ресурсы используемые при обработке запроса поддерживают ассинхроннный API, иначе смысла особого нет, потому как даже если столкнувшись с синхронным ресурсом увести запрос в отдельный тредпул, то один черт не тред контейнера так тред из этого пула будет зря ждать, так что в вашем случае смысла особого нету.
 
 
Сообщения:429
Спасибо!
А в принципе конструкции типа Thread.sleep(...) не выглядят моветоном?
Как-то весь программизм развивается в сторону большего и большего абстрагирования, а тут такая низкоуровневая вещь как "Спать!"
 
 
Сообщения:2391
Выглядит, но иногда понимание факта, что архитектура кривая, нисколько не приблежает к ответу а что же делать. Просто положите эту проблему на стек и помните о ней, может быть когда-нибудь найдется решение, например вы заиспользуете возможности СУБД поработе с событиями, или ответите себе на вопрос почему потребность в оповещении о некотором событии есть, но мутаторы приводящий некоторый ресурс в состояние когда событие наступает не оповещают скажем через месседж брокер потенциальных получателей события. Но даже не факт, что когда правильное видение архитектуры сформируется, то будет разумно выпилить sleep, может быть оставить его будет разумнее как по трудозатратам, так и по ресурсам.
Изменен:07 авг 2017 15:42
 
 
Сообщения:841
JMS, если присмотреться - тоже синхронный. Например, при публикации сообщений идет блокировка на методе send, пока не будет получен ответ от брокера. Метод receive также блокирует выполнение, пока не будет получен ответ от брокера. Асинхронность здесь другого рода, не протокольная.

Нет ничего проще, чем заблудиться в иллюзиях, нет ничего сложнее, чем освободиться от них.
 
 
Сообщения:9570
По-моему вопрос достаточно ясно говорит про асинхронную интеграцию, а не асинхронный IO :)
zAlexandr:
Как-то весь программизм развивается в сторону большего и большего абстрагирования, а тут такая низкоуровневая вещь как "Спать!"
Можно глянуть на Spring Retry если хочется абстрагирования и/или декларативности.
zAlexandr:
Но тогда получается, что мы держим поток входного запроса (а по сути спим).
Клиента тоже можно научить поллить если ты засыпаешь надолго.
 
 
Сообщения:2391
Роман Осипов это вообще-то как напишешь, в методы посылки и приема можно использовать коллбэки.
Изменен:08 авг 2017 07:26
 
 
Сообщения:2391
Староверъ:
По-моему вопрос достаточно ясно говорит про асинхронную интеграцию, а не асинхронный IO :)

Отнюдь. Если внимательно перечитать первый пост, то zAlexandr беспокоится о том, что у него занят рабочий поток контейнера, а то что клиент в это время ждёт ответа и возможно у него тоже висит какой-то тред его не беспокоит. Так что, ассинхронная интеграция это лишь один из вариантов решения, причем сопровождающееся переделыванием архитектуры и что хуже не только архитектуры но и API.

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

Хотя конечно при определенных условиях смысл заморочиться может появится и при работе с синхронным ресурсом, например: условие выхода из цикла опроса чаще ложно, чем положительно, время ожидания положительного срабатывания значительное, например несколько секунд, а запросы данного типа приходят от клиента достаточно часто, чтобы исчерпать весь тредпул контейнера. zAlexandr если это ваш случай, то можно конечно написать немного конкаренси кода, для решения проблемы, не меняя API и архитектуры.
Изменен:08 авг 2017 08:03
 
 
Сообщения:841
Vermut:
Роман Осипов это вообще-то как напишешь, в методы посылки и приема можно использовать коллбэки.

Блокировка все равно будет, пусть не в этом потоке, а в обслуживающем коллбэк. Кроме того, асинхронность и сохранение порядка обработки сообщений, как это требуется во многих применениях JMS, по большому счету несовместимы.
Интересно, есть какие-нибудь фреймворки, реализующие централизованное условное ожидание на основе подобной Event Dispatcher Thread?

Нет ничего проще, чем заблудиться в иллюзиях, нет ничего сложнее, чем освободиться от них.
 
 
Сообщения:429
Староверъ:
Можно глянуть на Spring Retry если хочется абстрагирования и/или декларативности.

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

Да, есть такая обеспокоенность. Тем более, что ожидание до минуты может быть. Пока это не проблема, но и проект еще только набирает обороты
Спасибо всем за обсуждение!
 
 
Сообщения:2391
zAlexandr:
Да, есть такая обеспокоенность. Тем более, что ожидание до минуты может быть.

А какое прогнозируемое время ответа от СУБД, и какую паузу планируете делать между повторными запросами?
 
 
Сообщения:429
В том и дело, что я запускаю некий work-flow на третьей стороне и чекаю статус: "уже готово?"
выглядит как рукожопинск какой-то.
 
 
Сообщения:2391
В общем если напишете что-то похожее как в примере ниже, то Вам станет безопаснее, риск исчерпать все треды контейнера уйдёт. Пример я сделал просто на основе Servlet API, и JAX-WS и JAX-RS имеют сопостовимые возможности по асинхронной обработке запросов, третьесторонний ресурс представлен заглушкой ExternalResource.
import javax.servlet.AsyncContext;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.*;

public class AsyncServlet extends HttpServlet {
    
    private ScheduledExecutorService scheduler;
    private int maxWaitDurationSeconds;
    private int pauseSeconds;
    private ExternalResource externalResource;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        ServletContext context = config.getServletContext();
        this.scheduler = (ScheduledExecutorService) context.getAttribute("scheduler");
        this.maxWaitDurationSeconds = (int) context.getAttribute("maxWaitDurationSeconds");
        this.pauseSeconds = (int) context.getAttribute("pauseSeconds");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String id = req.getParameter("id");
        
        // первый запрос делаем оптимистично синхронно
        Optional<String> optimisticResult = externalResource.getSomething(id);
        if (optimisticResult.isPresent()) {
            // если с первого раза получили положительный ответ
            resp.setStatus(200);
            resp.getWriter().println(result.get());
            return;
        }
        
        // иначе даем знать контейнеру что будем обрабатывать запрос ассинхронно
        final AsyncContext asyncContext = req.startAsync();
        
        // завершим обработку запроса когда закомплетится следующая фьюча
        CompletableFuture<String> resultFuture = new CompletableFuture<>();
        resultFuture.whenComplete((something, exception) -> {
            HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse();
            if (exception == null) {
                asyncResponse.setStatus(200);
                // TODO write something to response body
            } else {
                asyncResponse.setStatus(500);
                // TODO write something to response body
            }
            asyncContext.complete();
        });
        
        // уводим дальнейшую обработку запроса в другой тредпул
        long deadLineMillis = System.currentTimeMillis() + (maxWaitDurationSeconds - pauseSeconds) * 1000;
        Runnable pollRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Optional<String> result = externalResource.getSomething(id);
                    if (result.isPresent()) {
                        resultFuture.complete(result.get());
                        return;
                    } else {
                        if (System.currentTimeMillis() < deadLineMillis) {
                            // ещё есть шанс получить результат позже, просто решедулим себя
                            scheduler.schedule(this, pauseSeconds, TimeUnit.SECONDS);
                        } else {
                            // исчерпали все попытки
                            resultFuture.completeExceptionally(new TimeoutException());
                        }
                    }
                } catch (Exception e) {
                    resultFuture.completeExceptionally(e);
                    return;
                }
            }
        };
        scheduler.schedule(pollRunnable, pauseSeconds, TimeUnit.SECONDS);
    }
    
}
Изменен:10 авг 2017 21:02
 
Модераторы:Нет
Сейчас эту тему просматривают:Нет