Comment puis-je arrêter les pools Spring task executor/scheduler avant que tous les autres beans de l'application web ne soient détruits?
Dans une application Web Spring, j'ai plusieurs beans de couche DAO et de couche de service. Un bean de couche de service a annoté les méthodes @Async / @ Scheduled. Ces méthodes dépendent d'autres haricots (autowired). J'ai configuré deux pools de threads en XML:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="2" />
<property name="maxPoolSize" value="5" />
<property name="queueCapacity" value="5" />
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
</property>
</bean>
<bean id="taskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="10" />
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
</property>
</bean>
<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
Tout fonctionne comme prévu. Mon problème est que je ne peux pas obtenir un arrêt propre des pools de tâches pour fonctionner. Les tâches fonctionnent sur la base de données et sur le système de fichiers. Lorsque j'arrête l'application web, il faut un certain temps avant qu'elle ne soit arrêtée. Ce indique que la propriété waitForTasksToCompleteOnShutdown
fonctionne. Cependant, je reçois IllegalStateExceptions dans le journal indiquant que certains beans sont déjà détruits mais que certains threads de tâches de travail sont toujours en cours d'exécution et qu'ils échouent car leurs dépendances sont détruites.
Il y a un problème JIRA qui pourrait être pertinent: SPR-5387
Ma question Est: existe-t-il un moyen de dire à Spring d'initialiser les beans de l'exécuteur/planificateur de tâches en dernier ou Existe-t-il un moyen de dire à Spring de les détruire en premier?
Ma compréhension est que la destruction a lieu dans l'ordre d'initialisation inversé. Par conséquent, le haricot init'ed dernier sera détruit en premier. Si les beans du pool de threads sont détruits en premier, Toutes les tâches en cours d'exécution se terminent et peuvent toujours accéder aux beans dépendants.
J'ai également essayé d'utiliser l'attribut depends-on sur les pools de threads se référant à mon bean de service qui a les annotations @ Async et @Scheduled. On dirait qu'ils ne sont jamais exécutés alors et je ne comprends pas erreurs d'initialisation de contexte. Je suppose que le bean de service annoté a en quelque sorte besoin de ces pools de threads initialisés en premier et si j'utilise depends-on, j'inverse l'ordre et les rend non fonctionnels.
5 réponses
Deux façons:
Avoir un haricot mettre en œuvre
ApplicationListener<ContextClosedEvent>
. {[2] } sera appelé avant que le contexte et tous les beans soient détruits.Avoir un haricot mettre en œuvre Cycle de vie ou SmartLifecycle. {[3] } sera appelé avant que le contexte et tous les beans soient détruits.
De toute façon, vous pouvez arrêter les tâches avant que le mécanisme de destruction des haricots ait lieu.
Par exemple:
@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
@Autowired ThreadPoolTaskExecutor executor;
@Autowired ThreadPoolTaskScheduler scheduler;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
scheduler.shutdown();
executor.shutdown();
}
}
(Edit: méthode fixe la signature)
J'ai ajouté le code ci-dessous pour terminer les tâches que vous pouvez utiliser. Vous pouvez modifier les numéros de nouvelle tentative.
package com.xxx.test.schedulers;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import com.xxx.core.XProvLogger;
@Component
class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> , ApplicationContextAware,BeanPostProcessor{
private ApplicationContext context;
public Logger logger = XProvLogger.getInstance().x;
public void onApplicationEvent(ContextClosedEvent event) {
Map<String, ThreadPoolTaskScheduler> schedulers = context.getBeansOfType(ThreadPoolTaskScheduler.class);
for (ThreadPoolTaskScheduler scheduler : schedulers.values()) {
scheduler.getScheduledExecutor().shutdown();
try {
scheduler.getScheduledExecutor().awaitTermination(20000, TimeUnit.MILLISECONDS);
if(scheduler.getScheduledExecutor().isTerminated() || scheduler.getScheduledExecutor().isShutdown())
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has stoped");
else{
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has not stoped normally and will be shut down immediately");
scheduler.getScheduledExecutor().shutdownNow();
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has shut down immediately");
}
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Map<String, ThreadPoolTaskExecutor> executers = context.getBeansOfType(ThreadPoolTaskExecutor.class);
for (ThreadPoolTaskExecutor executor: executers.values()) {
int retryCount = 0;
while(executor.getActiveCount()>0 && ++retryCount<51){
try {
logger.info("Executer "+executor.getThreadNamePrefix()+" is still working with active " + executor.getActiveCount()+" work. Retry count is "+retryCount);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!(retryCount<51))
logger.info("Executer "+executor.getThreadNamePrefix()+" is still working.Since Retry count exceeded max value "+retryCount+", will be killed immediately");
executor.shutdown();
logger.info("Executer "+executor.getThreadNamePrefix()+" with active " + executor.getActiveCount()+" work has killed");
}
}
@Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
@Override
public Object postProcessAfterInitialization(Object object, String arg1)
throws BeansException {
return object;
}
@Override
public Object postProcessBeforeInitialization(Object object, String arg1)
throws BeansException {
if(object instanceof ThreadPoolTaskScheduler)
((ThreadPoolTaskScheduler)object).setWaitForTasksToCompleteOnShutdown(true);
if(object instanceof ThreadPoolTaskExecutor)
((ThreadPoolTaskExecutor)object).setWaitForTasksToCompleteOnShutdown(true);
return object;
}
}
J'ai eu des problèmes similaires avec les threads en cours de démarrage dans Spring bean. Ces threads ne se fermaient pas correctement après avoir appelé executor.shutdownNow() dans la méthode @ PreDestroy. Donc, la solution pour moi était de laisser le fil finsih avec IO déjà commencé et ne plus commencer IO, une fois que @PreDestroy a été appelé. Et voici la méthode @ PreDestroy. Pour ma demande, l'attente de 1 seconde était acceptable.
@PreDestroy
public void beandestroy() {
this.stopThread = true;
if(executorService != null){
try {
// wait 1 second for closing all threads
executorService.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Ici, j'ai expliqué tous les problèmes rencontrés en essayant de fermer fils. http://programtalk.com/java/executorservice-not-shutting-down/
S'il s'agit d'une application web, vous pouvez également utiliser L'interface ServletContextListener.
public class SLF4JBridgeListener implements ServletContextListener {
@Autowired
ThreadPoolTaskExecutor executor;
@Autowired
ThreadPoolTaskScheduler scheduler;
@Override
public void contextInitialized(ServletContextEvent sce) {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
scheduler.shutdown();
executor.shutdown();
}
}
Nous pouvons ajouter la propriété "AwaitTerminationSeconds" pour taskExecutor et taskScheduler comme ci-dessous,
<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />
<property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />
La documentation de la propriété "waitForTasksToCompleteOnShutdown" indique que lorsque shutdown est appelé
"L'arrêt des conteneurs de Spring se poursuit pendant que les tâches en cours sont terminées. Si vous voulez que cet exécuteur bloque et attende la fin des tâches avant que le reste du conteneur continue à s'arrêter - par exemple afin de conserver d'autres ressources que vos tâches peut avoir besoin de -, définissez la propriété "awaitTerminationSeconds" au lieu de ou en plus de cette propriété."
Il est donc toujours conseillé d'utiliser les propriétés waitfortaskstocompleteonshutdown et awaitTerminationSeconds ensemble. La valeur de awaitTerminationSeconds dépend de notre application.