Работали ли вы с аннотацией @Async в Spring
Что проверяют
Понимаете ли вы, что асинхронность тут сделана прокси-объектом, а не магией языка. Отсюда вытекает всё остальное: почему вызов иногда молча выполняется синхронно, на каком пуле он работает, куда девается исключение и что происходит с транзакцией.
Как отвечать
Опишите устройство: @EnableAsync в конфигурации приложения включает обработку, @Async на публичном методе бина переносит выполнение в исполнителя задач. Возвращать метод может void, Future или CompletableFuture.
Ключевые детали:
- прокси: асинхронным станет только вызов, пришедший снаружи бина. Метод, вызванный из соседнего метода того же класса, отработает в текущем потоке;
- пул: задавайте свой
ThreadPoolTaskExecutorс понятными размерами, ограниченной очередью и префиксом имён потоков, а в аннотации указывайте его имя. В приложении без автоконфигурации по умолчанию берётся исполнитель, создающий новый поток на каждый вызов; - ошибки: из метода с
voidисключение никуда не долетает, нужен обработчик непойманных исключений. УCompletableFutureошибка придёт при получении результата; - контекст: данные авторизации, диагностический контекст логов и область запроса в новый поток не переезжают, их переносят явно.
@Async("reportExecutor")
public CompletableFuture<Report> build(long id) { ... }Где спотыкаются
Зовут аннотированный метод из соседнего метода того же класса. Прокси в вызове не участвует, всё выполняется синхронно, а разработчик ищет проблему в настройках пула. Лечится выносом в отдельный бин.
Второе: остаются на исполнителе по умолчанию и не смотрят на очередь. Когда она не ограничена, задачи копятся в памяти, число потоков при этом не растёт, и под нагрузкой сервис отвечает всё позже, пока не упирается в память.
Третье: рассчитывают, что транзакция вызывающего кода продолжится в фоне. Она остаётся в исходном потоке, асинхронный метод начинает свою или работает без неё, а ленивые связи, которые пытаются подгрузиться в новом потоке, дают ошибку инициализации.