web-dev-qa-db-fra.com

Comment puis-je arrêter les pools d'exécuteur/programmateur de tâche Spring avant que tous les autres beans de l'application Web soient détruits?

Dans une application Web Spring, j'ai plusieurs beans 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 beans (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 complet des pools de tâches pour fonctionner. Les tâches opèrent 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 de l'arrêter. Cela indique que la propriété waitForTasksToCompleteOnShutdown fonctionne. Cependant, le journal indique IllegalStateExceptions 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.

Un problème JIRA qui pourrait être pertinent: SPR-5387

Ma question est la suivante: existe-t-il un moyen d'indiquer à Spring d'initialiser les beans de l'exécuteur de tâche/du planificateur en dernier ou existe-t-il un moyen d'indiquer à Spring de les détruire en premier?

Je crois comprendre que la destruction a lieu dans un ordre init inversé. Par conséquent, le dernier haricot initié sera détruit en premier. Si les beans du pool de threads sont d'abord détruits, toutes les tâches en cours d'exécution seront terminées et pourront toujours accéder aux beans dépendants.

J'ai également essayé d'utiliser l'attribut depend-on sur les pools de threads faisant référence à mon bean service, qui comporte les annotations @Async et @Scheduled. On dirait qu'elles ne sont jamais exécutées à ce moment-là et que je n'ai pas d'erreur d'initialisation de contexte. Je suppose que le bean de service annoté a besoin d'une certaine manière que ces pools de threads soient d'abord initialisés et que si j'utilise dépend de l'inverse, je inverse l'ordre et je les rends non fonctionnels.

39
tvirtualw

Deux manières:

  1. Avoir un bean implémenter ApplicationListener<ContextClosedEvent>. onApplicationEvent() sera appelé avant le contexte et tous les haricots sont détruits.

  2. Avoir un bean implémenter Lifecycle ou SmartLifecycle . stop() sera appelé avant le contexte et tous les haricots sont détruits. 

Dans les deux cas, vous pouvez fermer la tâche avant que le mécanisme de destruction des haricots ne se produise.

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();
    }       
}

(Édition: signature de méthode fixe)

50
sourcedelica

J'ai ajouté le code ci-dessous pour terminer les tâches que vous pouvez utiliser. Vous pouvez changer le nombre de tentatives.

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;
}

}

7
fatih tekin

J'ai eu des problèmes similaires avec les threads démarrés dans Spring Bean. Ces threads ne se fermaient pas correctement après avoir appelé executor.shutdownNow () dans la méthode @PreDestroy. La solution pour moi était donc de laisser le fil se terminer avec IO déjà démarré et de ne plus démarrer d'E/S, une fois @PreDestroy appelé. Et voici la méthode @PreDestroy. Pour ma demande, l'attente d'une 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 lors de la fermeture de threads. http://programtalk.com/Java/executorservice-not-shutting-down/

3
awsome

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 l'arrêt est appelé 

"L'arrêt du conteneur de Spring se poursuit pendant que les tâches en cours sont terminées. Si vous voulez que cet exécuteur bloque et attend la fin des tâches avant que le reste du conteneur ne se ferme, par exemple pour conserver d'autres ressources que vos tâches peut avoir besoin de -, définissez la propriété "waitTerminationSeconds" au lieu de ou en plus de cette propriété. "

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.html#setWaitForTasksToCompleteOnShutdown-boolean

Il est donc toujours conseillé d'utiliser les propriétés waitForTasksToCompleteOnShutdown et waitTerminationSeconds ensemble. La valeur de waitTerminationSeconds dépend de notre application.

0
Manjunath D R