22

I have a WAR file deployed to Tomcat server, one of the class will get called at start up time, then the init() method will schedule a timer to fire every 5 hours to perform some tasks.

My init() code looks like this:

public void init()
{
    TimerTask parserTimerTask = new TimerTask() {

        @Override
        public void run() {
            XmlParser.parsePage();
        }
    };

    Timer parserTimer = new Timer();
    parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}

My application runs without problem, but when I shutdown the Tomcat using /etc/init.d/tomcat7 stop, then I check the log (catalina.out) it has a entry like this:

SEVERE: The web application [/MyApplication] appears to have started a thread named [Timer-0] but has failed to stop it. This is very likely to create a memory leak.

I understand this is caused by me schedule the timer, but my question is:

  1. I didn't set setDeamon to true, so shouldn't the timer prevent Tomcat from shutting down, rather than left running?
  2. Can I, in my application, detect Tomcat is going to be shutdown and cancel my timer?
  3. What are the other solutions I can use to take care of this issue?

Thanks!

UPDATE

I changed my code to the following based on some search and DaveHowes's answer.

Timer parserTimer;
TimerTask parserTimerTask;

public void init()
{
    parserTimerTask = new TimerTask() {

        @Override
        public void run() {
            XmlParser.parsePage();
        }
    };

    parserTimer = new Timer();
    parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}

@Override
public void contextDestroyed(ServletContextEvent arg0) {
    Logger logger = Logger.getRootLogger();
    logger.info("DETECT TOMCAT SERVER IS GOING TO SHUT DOWN");
    logger.info("CANCEL TIMER TASK AND TIMER");

    otsParserTimerTask.cancel();

    otsParserTimer.cancel();

    logger.info("CANCELING COMPLETE");
}

@Override
public void contextInitialized(ServletContextEvent arg0) {

}

Now my new question:

  1. I cancel TimerTask first then Timer, is this correct?
  2. Are there other thing I should do?

Thanks!

UPDATE

It doesn't work. I put some logging statement in the contextDestroyed() method, after I shutdown Tomcat, the log file only has the following:

PowderGodAppWebService -> [07 Feb 2012 04:09:46 PM] INFO (PowderGodAppWebService.java:45):: DETECT TOMCAT SERVER IS GOING TO SHUT DOWN PowderGodAppWebService -> [07 Feb 2012 04:09:46 PM] INFO (PowderGodAppWebService.java:46):: CANCEL TIMER TASK AND TIMER

CANCELING COMPLETE is not there.

I also checked processes that are running (I'm not a Linux expert so I just use Mac's Activity Monitor.

  • Make sure no java process is running
  • Start Tomcat, note the PID of that java process
  • Stop Tomcat
  • Found the Tomcat process is gone
  • Start Tomcat, note the PID of that java process
  • Deploy my war file
  • Sample the process, see [Timer-0] thread is there
  • Shutdown Tomcat
  • Found that the process is still there
  • Sample the process
  • See [Timer-0] is still there

FIXED

I changed my code to parserTimer = new Timer(true); so that my timer runs as a daemon thread because the contextDestroyed() gets called after Tomcat actually shuts down.

"All servlets and filters will have been destroyed before any ServletContextListeners are notified of context destruction."

http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContextListener.html

Derek Li
  • 2,921
  • 2
  • 22
  • 37
  • Shouldn't those cancels be in contextDestroyed() ? – DaveH Feb 07 '12 at 08:51
  • Yes... I guess it's time for bed, thanks for pointing out! – Derek Li Feb 07 '12 at 08:52
  • 1
    TimerTask#cancel won't interrupt the task if it is currently running - it'll just make sure that it'll never run again - but apart from that, I'd say that you'd be fine with that – DaveH Feb 07 '12 at 08:55
  • since you used `parserTimer = new Timer(true);` does that mean the parser will continue running as a background process even after Tomcat stops? Or does it mean that it blocks Tomcat from stopping until it is finished? I'm in a very similar situation - hoping to reliably shutdown a thread when Tomcat is being stopped, and I'm not sure what works. – Don Cheadle Jan 08 '15 at 17:41

4 Answers4

66

Do not use Timer in an Java EE environment! If the task throws a runtime exception, then the whole Timer is killed and won't run anymore. You basically needs to restart the whole server to get it to run again. Also, it is sensitive to changes in the system clock.

Use ScheduledExecutorService instead. It's not sensitive to exceptions thrown in tasks nor to changes in system clock. You can shutdown it by its shutdownNow() method.

Here's an example of how the entire ServletContextListener implementation can look like (note: no registration in web.xml required thanks to the new @WebListener annotation):

@WebListener
public class BackgroundJobManager implements ServletContextListener {

    private ScheduledExecutorService scheduler;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new YourParsingJob(), 0, 5, TimeUnit.HOUR);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        scheduler.shutdownNow();
    }

}
Jeff Olson
  • 5,792
  • 2
  • 20
  • 26
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • 2
    Wow! This answer was *amazingly* helpful. Thank you very much. For other readers, one point of clarification… The `new YourParsingJob()` should be an instance of the interface [Runnable](http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html) as the JavaDoc explains. – Basil Bourque Feb 23 '14 at 09:25
  • And what happend if i need access to some resource like an `EntityManager`, can you have acces to this or you have to use `@Singleton` with `@Startup` anntotation? – rekiem87 Mar 11 '14 at 00:38
  • 2
    @rekiem87: if you have EJB at hands, use `@Schedule` instead. Examples: http://stackoverflow.com/a/8482933 and http://stackoverflow.com/q/7499769 (bottom part). Current question is about Tomcat which doesn't have JPA and EJB. Be careful in finding answers ... – BalusC Mar 11 '14 at 07:01
  • 1
    Small correction, Tomcat project comes with TomEE for enterprise. It has prepackaged CDI, EJB and JPA. (see http://openejb.apache.org/apache-tomee.html) – user1697575 Oct 24 '14 at 18:15
  • 1
    @user1697575: TomEE !== Tomcat. – BalusC Oct 28 '14 at 06:39
  • To clarify, [Apache TomEE](https://en.wikipedia.org/wiki/Apache_TomEE) is a *superset* of [Apache Tomcat](https://en.wikipedia.org/wiki/Apache_Tomcat), thus the cute choice of name. The idea was to start with Tomcat (for Servlets & JSP, and web server), then add the necessary Java EE related libraries to meet the requirements of the spec for [Java EE Web Profile](https://en.wikipedia.org/wiki/Java_Platform,_Enterprise_Edition#Web_profile) (a subset of full Java EE). As of 2017-05, TomEE 7.0.3 is based on Tomcat 8.5.11. In other words: `( Tomcat + some Java EE components ) = TomEE` – Basil Bourque May 26 '17 at 23:22
1

The servlets destroy method is called as the servlet is about to be unloaded. You could cancel the timer from within there, providing that you altered the scope of the parserTimer itself to make it an instance variable. I don't see a problem with that provided that you access it only from within init and destroy.

The servlet engine is free to unload the servlet whenever it sees fit, but in pratice I'm only ever seen it called as the servlet engine is stopped - other people may have other experiences of this though that might prove me wrong.

DaveH
  • 6,831
  • 5
  • 28
  • 48
-1

Try to use framework for scheduling.

If you adapt Spring Framework you could use the build in scheduling capabilities.

When scheduling with Spring I had never any problem stopping application server.

takacsot
  • 1,563
  • 2
  • 16
  • 29
-2

Put parseTimer.purge() in your onContetexyDestroyed.It will remove all the timers in the queue if they exist.

Rookie
  • 8,182
  • 15
  • 55
  • 89