Jak Mogę wyłączyć Pule wykonawców/harmonogramów zadań Spring, zanim wszystkie inne fasole w aplikacji internetowej zostaną zniszczone?
W wiosennej aplikacji internetowej mam kilka fasoli DAO i service layer. Jedna z warstw usług ma przypisane metody @Async / @Scheduled. Metody te zależą od innych (autowirowanych) ziaren. Skonfigurowałem dwie pule wątków w 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"/>
Wszystko działa zgodnie z oczekiwaniami. Mój problem polega na tym, że nie mogę uzyskać czystego wyłączenia pul zadań do pracy. Zadania działają w bazie danych i w systemie plików. Kiedy Zatrzymuję aplikację webową, zajmuje to trochę czasu, zanim zostanie zatrzymana. To wskazuje, że właściwość waitForTasksToCompleteOnShutdown
działa. Jednak dostaję IllegalStateExceptions w dzienniku wskazujące, że niektóre fasolki są już zniszczone, ale niektóre wątki zadań workera są nadal wykonywane i zawodzą, ponieważ ich zależności są zniszczone.
Istnieje problem JIRA, który może być istotny: SPR-5387
Moje pytanie brzmi: Czy Jest jakiś sposób żeby Spring zainicjował zadanie executor/scheduler na końcu czy jest jakiś sposób żeby Spring je zniszczył najpierw?
Rozumiem, że zniszczenie odbywa się w odwrotnej kolejności. W związku z tym fasola init'ed last zostanie zniszczona jako pierwsza. Jeśli wątek pool beans zostanie zniszczony jako pierwszy, wszystkie aktualnie wykonywane zadania zakończą się i nadal będą mogły uzyskać dostęp do zależnych beans.
Próbowałem również użyć atrybutu depends-on na pulach wątków odnoszących się do mojego serwisu bean, który ma adnotacje @Async i @Scheduled. Wydaje się, że nigdy nie są wykonywane wtedy i nie dostać błędy inicjalizacji kontekstu. Zakładam, że ten serwis w jakiś sposób potrzebuje najpierw tych pul wątków, a jak użyję to odwrócę kolejność i uczynię je niefunkcjonalnymi.
5 answers
Dwa sposoby:
Mieć Bean zaimplement
ApplicationListener<ContextClosedEvent>
.onApplicationEvent()
zostanie wywołana przed kontekstem i wszystkie ziarna zostaną zniszczone.Mieć implementację bean Lifecycle lub SmartLifecycle.
stop()
zostanie wywołana przed kontekstem i wszystkie ziarna zostaną zniszczone.
Tak czy siak możesz wyłączyć zadania, zanim zostanie uruchomiony mechanizm niszczenia fasoli.
Eg:
@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
@Autowired ThreadPoolTaskExecutor executor;
@Autowired ThreadPoolTaskScheduler scheduler;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
scheduler.shutdown();
executor.shutdown();
}
}
(Edit: Fixed method podpis)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-19 22:47:03
Dodałem poniżej kod, aby zakończyć zadania Można go używać. Możesz zmienić numery prób.
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;
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-12 10:26:43
Miałem podobne problemy z wątkami uruchamianymi w fasoli wiosennej. Te wątki nie zamykały się poprawnie po tym, jak zadzwoniłem do executora.shutdownNow () w metodzie @PreDestroy. Więc rozwiązaniem dla mnie było niech wątek finsih z IO już się rozpoczął i start no more IO, raz @ PreDestroy został wywołany. A oto metoda @PreDestroy. Dla mojej aplikacji oczekiwanie na 1 sekundę było dopuszczalne.
@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();
}
}
}
Tutaj wyjaśniłem wszystkie problemy napotykane podczas próby zamknięcia wątki. http://programtalk.com/java/executorservice-not-shutting-down/
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-09 09:10:39
Jeśli ma to być aplikacja internetowa, Możesz również użyć interfejsu 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();
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-05 09:46:45
Możemy dodać właściwość "Waitterminationseconds" zarówno dla taskExecutor, jak i taskScheduler, jak poniżej,
<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />
<property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />
Dokumentacja dla właściwości "waitForTasksToCompleteOnShutdown" mówi, że po wywołaniu shutdown
"zamykanie kontenerów Springa jest kontynuowane podczas wykonywania bieżących zadań. Jeśli chcesz, aby ten executor blokował i czekał na zakończenie zadań, zanim reszta kontenera nadal się wyłączy - np. w celu utrzymania innych zasobów, które twoje zadania może wymagać -, ustawić właściwość "waitterminationseconds" zamiast lub jako dodatek do tej właściwości."
Dlatego zawsze zaleca się używanie właściwości waitForTasksToCompleteOnShutdown i waitterminationseconds razem. Wartość oczekiwanych sekund zależy od naszej aplikacji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-18 21:22:46