Spring Boot: изящное завершение работы, контролируя порядок завершения с участием MongoClient.

У меня есть приложение Spring Boot, которое порождает множество потоков с использованием AsyncTaskExecutor (число предопределено)

Потоки выполняют бесконечный цикл, который читается из некоторых объектов очереди и обработки, поэтому у меня действительно нет механизма политики отклонения (например, ThreadPool, который принимает tasks)

Проблема в том, что когда приложение закрывается, потоки могут (и, вероятно) быть заняты обработкой элемента, который включает в себя операции для Mongo с использованием MongoTemplate.

Поэтому, когда приложение закрывается, MongoClient автоматически close() обрабатывается, а затем я получаю некоторые ошибки от Mongo, например:

java.lang.IllegalStateException: The pool is closed
    at com.mongodb.internal.connection.ConcurrentPool.get(ConcurrentPool.java:137)
    at com.mongodb.internal.connection.DefaultConnectionPool.getPooledConnection(DefaultConnectionPool.java:262)
    at com.mongodb.internal.connection.DefaultConnectionPool.get(DefaultConnectionPool.java:103)
    at com.mongodb.internal.connection.DefaultConnectionPool.get(DefaultConnectionPool.java:92)
    at com.mongodb.internal.connection.DefaultServer.getConnection(DefaultServer.java:85)

Как я могу закрыть приложение изящно? например прерывать потоки, пока не закрывая MongoClient?

КОД:

Создание бина:

@Bean
AsyncTaskExecutor getTaskExecutor() {
    SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
    return executor;
}

Исполнение просто:

executor.execute(runnable);

person yaseco    schedule 13.04.2020    source источник
comment
Покажите, как вы создаете темы. Вы должны иметь возможность переопределить isInterrupted и выполнить изящное отключение от монго.   -  person 123    schedule 13.04.2020


Ответы (1)


Не используйте SimpleAsyncTaskExecutor — SimpleAsyncTaskExecutor создает новый поток для каждого запроса, вместо этого используйте ThreadPoolTaskExecutor и настройте два свойства, указанные ниже.

/**
 * Set whether to wait for scheduled tasks to complete on shutdown,
 * not interrupting running tasks and executing all tasks in the queue.
 * <p>Default is "false", shutting down immediately through interrupting
 * ongoing tasks and clearing the queue. Switch this flag to "true" if you
 * prefer fully completed tasks at the expense of a longer shutdown phase.
 * <p>Note that Spring's container shutdown continues while ongoing tasks
 * are being completed. If you want this executor to block and wait for the
 * termination of tasks before the rest of the container continues to shut
 * down - e.g. in order to keep up other resources that your tasks may need -,
 * set the {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"}
 * property instead of or in addition to this property.
 * @see java.util.concurrent.ExecutorService#shutdown()
 * @see java.util.concurrent.ExecutorService#shutdownNow()
 */
public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
    this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
}

/**
 * Set the maximum number of seconds that this executor is supposed to block
 * on shutdown in order to wait for remaining tasks to complete their execution
 * before the rest of the container continues to shut down. This is particularly
 * useful if your remaining tasks are likely to need access to other resources
 * that are also managed by the container.
 * <p>By default, this executor won't wait for the termination of tasks at all.
 * It will either shut down immediately, interrupting ongoing tasks and clearing
 * the remaining task queue - or, if the
 * {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"}
 * flag has been set to {@code true}, it will continue to fully execute all
 * ongoing tasks as well as all remaining tasks in the queue, in parallel to
 * the rest of the container shutting down.
 * <p>In either case, if you specify an await-termination period using this property,
 * this executor will wait for the given time (max) for the termination of tasks.
 * As a rule of thumb, specify a significantly higher timeout here if you set
 * "waitForTasksToCompleteOnShutdown" to {@code true} at the same time,
 * since all remaining tasks in the queue will still get executed - in contrast
 * to the default shutdown behavior where it's just about waiting for currently
 * executing tasks that aren't reacting to thread interruption.
 * @see java.util.concurrent.ExecutorService#shutdown()
 * @see java.util.concurrent.ExecutorService#awaitTermination
 */
public void setAwaitTerminationSeconds(int awaitTerminationSeconds) {
    this.awaitTerminationSeconds = awaitTerminationSeconds;
}

Соответствующая часть

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

Вы можете настроить с помощью автоматической настройки Spring для управления свойствами выполнения задачи (предпочтительно) или программно с аннотацией @Bean

Spring boot в 2.1.0 обеспечивает автоматическую настройку для исполнителей задач и использует поддержку @EnableAsync и Spring MVC Async.

Из приложения не требуется конфигурация bean-компонента-исполнителя задач/webMvcConfigurer. Так что удалите тот, который у вас есть, и это должно быть хорошо.

Вы можете настроить, используя файл application properties/yml с spring.task.execution.*.

spring.task.execution.shutdown.await-termination=true
spring.task.execution.shutdown.await-termination-period=60

Полный список можно найти here

Подробнее here и here

OR

@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setCorePoolSize(5);
    taskExecutor.setMaxPoolSize(5); 
    taskExecutor.waitForTasksToCompleteOnShutdown(true);
    taskExecutor.setAwaitTerminationSeconds(60);
    return taskExecutor;
  }
person s7vr    schedule 16.04.2020
comment
На самом деле я использовал ThreadPoolTaskExecutor с этими параметрами. Дело в том, что я все еще получаю ошибки от клиента mongo, требуя от меня попытки поймать IllegalStateException каждый раз, когда я использую, скажем, MongoTemplate. Есть ли способ обойти это? - person yaseco; 16.04.2020
comment
Это странно. Если бы вы могли поделиться примером, воспроизводящим вашу проблему (предпочтительно проект github), я мог бы изучить ее глубже, а также, пожалуйста, обновите ваш пост, чтобы показать, что у вас есть прямо сейчас. Требуется больше деталей. Не могли бы вы использовать аннотацию @Async для выполнения своих задач? Кроме того, как запланированы эти задачи? Вы должны позволить Spring управлять задачами для вас. - person s7vr; 16.04.2020
comment
Некоторые примеры здесь - person s7vr; 16.04.2020
comment
Я попробую @Async, как вы предложили - person yaseco; 16.04.2020
comment
Сагар, не могли бы вы кратко объяснить, как @Async решит проблему? - person yaseco; 16.04.2020
comment
После того, как вы пометите метод как @Async, при вызове метода он будет работать в отдельном потоке (поток, заимствованный из пула потоков) и управляемый Spring. Поэтому, как только ваша весенняя загрузка завершится, она изящно завершит поток, выполняющий задачу. Вам понадобится @EnableAsync, чтобы включить асинхронную обработку. - person s7vr; 17.04.2020
comment
@SagarVeeram извините, но Spring не корректно завершает асинхронную задачу. Я тестировал, и асинхронный поток прерывался до его завершения. - person Duc Le; 08.07.2020