54

In a Spring web application I have several DAO and service layer beans. One service layer bean has annotated @Async / @Scheduled methods. These methods depend on other (autowired) beans. I have configured two thread pools in 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"/>

Everything works as expected. My problem is that I cannot get a clean shutdown of the task pools to work. The tasks operate on the database and on the file system. When I stop the web application it takes some time until it is stopped. This indicates that the waitForTasksToCompleteOnShutdown property works. However, I get IllegalStateExceptions in the log indicating that some beans are already destroyed but some worker task threads are still executing and they fail because their dependencies are destroyed.

There is a JIRA issue which might be relevant: SPR-5387

My question is: Is there a way to tell Spring to initialize the task executor/scheduler beans last or is there a way to tell Spring to destroy them first?

My understanding is that destruction takes place in reversed init order. Therefore the bean init'ed last will be destroyed first. If the thread pool beans are destroyed first, all currently executing tasks would finish and could still access dependent beans.

I have also tried using the depends-on attribute on the thread pools referring to my service bean which has the @Async and @Scheduled annotations. Seems like they are never executed then and I do not get context initialization errors. I assume the annotated service bean somehow needs these thread pools initialized first and if I use depends-on I reverse the order and make them non-functional.

tvirtualw
  • 963
  • 2
  • 8
  • 13
  • possibly this post helps? http://technicalmumbojumbo.wordpress.com/2011/04/13/spring-framework-an-introduction-part-ii-object-lifecycle-autowiring-internationalizationi18n/ I don't know. this is a really good question. Am curious how you get this resolved... – Peter Perháč Jul 06 '11 at 21:27
  • Thanks. See the answer of @ericacm and my comment. – tvirtualw Jul 07 '11 at 01:17
  • 1
    I was able to get around this by simply registering the shutdown method on destroy event. Programatically: http://docs.spring.io/spring/docs/3.2.0.RC1_to_3.2.0.RC2/changes/docdiffs_org.springframework.scheduling.annotation.html So following this : if you set the destroy attribute of the bean to shutdown, things should work – hellojava Dec 21 '14 at 13:56
  • Hi tvirtualw, i have doubt here, when we give both taskExecutor and taskScheduler to task annotation driven as you used, will they both use single ThreadPoolExecutor or two different ThreadPoolExecutor's? Because both taskExecutor and taskScheduler internally uses ThreadPoolExecutor. If they both use same ThreadPoolExecutor then why do we have corePoolSize = 2 for taskExecutor and poolSize = 10 for taskScheduler, because they both will refer to same thread pool size. And which value of these 2 sizes takes affect? Anyone can help me here. Thanks in advance. – Manjunath D R Jun 23 '16 at 10:38

5 Answers5

68

Two ways:

  1. Have a bean implement ApplicationListener<ContextClosedEvent>. onApplicationEvent() will get called before the context and all the beans are destroyed.

  2. Have a bean implement Lifecycle or SmartLifecycle. stop() will get called before the context and all the beans are destroyed.

Either way you can shut down the task stuff before the bean destroying mechanism takes place.

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 signature)

JeanValjean
  • 17,172
  • 23
  • 113
  • 157
sourcedelica
  • 23,940
  • 7
  • 66
  • 74
  • 1
    Thanks for the answer. Could you please be more specific in regard to my case? Should I create a new class extending the ThreadPoolTaskExecutor and implementing SmartLifecycle? Then let getPhase() return a high Integer value? How would I go about implementing the stop method? Just call the shutdown() method of the ThreadPoolTaskExecutor? What should I do with the Runnable parameter of the stop method? Could I also use a @PreDestroy annotation instead of the SmartLifecycle interface? A short example or a link to a code snippet would be highly appreciated. – tvirtualw Jul 06 '11 at 22:04
  • 3
    The above works. It seems the event fires twice. When I log a debug message on method execution, it gets logged twice. Calling the shutdown() method is not sufficient as indicated in the JIRA issue I have referenced. Beans will still be destroyed while the pool shuts down. Using the shutdownAndAwaitTermination example method in the JDK API docs of [ExecutorService](http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ExecutorService.html) did the trick. Instead of just calling scheduler.shutdown() I call shutdownAndAwaitTermination(scheduler.getScheduledExecutor()); – tvirtualw Jul 07 '11 at 01:12
  • are you using spring mvc? if you are the second event is from the DispatcherServlet context. you can also implement ApplicationContextAware to get the main app context and then check event->getApplicationContext against that value in your onApplicationEvent(). – sourcedelica Jul 07 '11 at 01:51
  • 6
    You could also specify a "destroy-method" on the bean itself. Not that for a ThreadPoolExecutor, there's no great method to call, since shutdown() and shutdownNow() don't guarantee the pool is fully terminated before returning. However, you could extend the ThreadPoolExecutor class for this purpose. But I think you're much better off with the ApplicationListener as above. However, you should probably also call executor.awaitTermination as well to make sure the pool is terminated before you exit the onApplicationEvent method. – Matt Jun 13 '12 at 02:44
  • when i use shutdown after awaitTermination call scheduler still continues to run. I want to make scheduler stop to submit tasks – fatih tekin Jul 11 '13 at 12:59
  • 1
    My mistake I should have called shutdown first before awaitTermination – fatih tekin Jul 11 '13 at 14:30
  • Does not work. exception: The web application [xx] appears to have started a thread named [Scheduler - 1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread: sun.misc.Unsafe.park(Native Method) java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093) – Arijeet Saha Jul 25 '16 at 12:38
  • Late to the party. But, Spring automatically calls public `close` and `shutdown` methods. [Ref](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-java-lifecycle-callbacks) – Mohnish Mar 01 '18 at 18:24
8

I have added below code to terminate tasks you can use it. You may change the retry numbers.

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

}

fatih tekin
  • 959
  • 10
  • 21
  • Line "Map schedulers = context.getBeansOfType(ThreadPoolTaskScheduler.class);" throws NullPointerException – gandra404 Aug 16 '15 at 14:47
  • this is probably context is no set since wiring in your project does not work. Can you add more stack trace? – fatih tekin Oct 23 '15 at 20:54
  • I have totally changed app so I can not reproduce it any more. – gandra404 Oct 26 '15 at 15:38
  • I am facing similar issue with my web application. I do see logger logging.. `03:44:08.727 [localhost-startStop-2] INFO c.a.o.rs.config.ContextClosedHandler - Scheduler task-scheduler- has stopped ` but still i get the same error in logs `SEVERE: The web application [/opulence-rs] appears to have started a thread named [pool-14-thread-2] but has failed to stop it. This is very likely to create a memory leak.` – harry Feb 28 '16 at 08:49
  • @sourcedelica has much simpler answer – fatih tekin Sep 28 '20 at 15:23
3

I had similar issues with the threads being started in Spring bean. These threads were not closing properly after i called executor.shutdownNow() in @PreDestroy method. So the solution for me was to let the thread finsih with IO already started and start no more IO, once @PreDestroy was called. And here is the @PreDestroy method. For my application the wait for 1 second was 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();
            }
        }
    }

Here I have explained all the issues faced while trying to close threads.http://programtalk.com/java/executorservice-not-shutting-down/

awsome
  • 2,143
  • 2
  • 23
  • 41
1

If it is going to be a web based application, you can also use the ServletContextListener interface.

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

    }

}

0

We can add "AwaitTerminationSeconds" property for both taskExecutor and taskScheduler as below,

<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />

<property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />

Documentation for "waitForTasksToCompleteOnShutdown" property says, when shutdown is called

"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 "awaitTerminationSeconds" property instead of or in addition to this property."

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

So it is always advised to use waitForTasksToCompleteOnShutdown and awaitTerminationSeconds properties together. Value of awaitTerminationSeconds depends on our application.

Kannan N
  • 3
  • 2
Manjunath D R
  • 371
  • 2
  • 8