littleK
littleK

Reputation: 20123

Trouble stopping a tomcat server that is running a web application with a while (true) loop

I'm developing a web application to be deployed onto Tomcat. When Tomcat is started, I use a servlet (in web.xml) to call a Java class:

<web-app>
    <display-name>Consumer</display-name>
    <servlet>
        <servlet-name>start</servlet-name>
        <servlet-class>com.test.sample.Consumer</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
</web-app>

My Consumer.java subscribes to a queue on an AMQP server. I achieve this by using a while (true) loop, which works fine in a standalone Java program. Itt also works in the context of the web application, but I can never stop my Tomcat server (within my NetBeans IDE), and I believe that the while loop is the culprit. Here is some code:

public class Consumer {
    public Consumer()
        consume();
    }

    private void consume()

        ...

        while (true) {
            // Await incoming messages from queue
            // Process message
        }

    }

}

Is there a better way to handle this? Or to signal a stop to break out of the loop?

Thanks!


Updated to use ServletContextListener:

public final class ApplicationListener implements ServletContextListener {

    private ScheduledExecutorService scheduler;

    public ApplicationListener() {
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        System.out.println("***** Stopping Consumer *****");
        scheduler.shutdownNow();
    }

    @Override
    public void contextInitialized(ServletContextEvent event) {

        System.out.println("***** Starting Consumer *****");

        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new ScheduledConsumer(), 0, 15000, TimeUnit.MILLISECONDS);

    }

    public class ScheduledConsumer implements Runnable {
        @Override
        public void run() {
            Consumer k = new Consumer();
            k.consumeOnce();
        }
    }
}

Upvotes: 1

Views: 2792

Answers (2)

Christopher Schultz
Christopher Schultz

Reputation: 20837

I have some suggestions, but they require that you modify your architecture a bit in order to more nicely play with your container environment.

Servlet containers support "listeners" that can get notification of various events. Specifically, one of them is the ServletContextListener which gets notified when the context (aka. webapp) is being brought into service (via the contextInitialized method) and when it is being brought out of service (via the contextDestroyed method).

My recommendation would be to do the following:

  1. Change your Consumer class's constructor so that it does not automatically call consume(); instead, add a public method like consumeOnce and don't use a loop at that level at all
  2. Write a ServletContextListener that has a Consumer and a Thread reference as members as well as a volatile boolean stop flag; in contextInitialized it should create a new Consumer object, then launch a new (daemon) thread that:
    • Calls Consumer.consumeOnce
    • Calls Thread.sleep for an appropriate amount of time
    • Loops over the previous 2 steps until the stop flag is true
  3. Have your ServletContextListener's contextDestroyed method set the stop flag to true and call Thread.interrupt on the running thread.

I'm sure I'm missing some exact details, but that's the general idea. When Tomcat shuts down, your code will be notified of the shutdown and you can cleanly terminate your own looping-thread. You may need to provide a way for the Consumer to abort an attempt to consume whatever it consumes (e.g. stop waiting to pull an object from an empty queue) if it doesn't abort when it gets a Thread.interrupt signal. (For instance if you use an Object.wait() in order to wait for a monitor notification, then you'll want to change that so it uses a wait with a timeout so that you won't block forever).

Upvotes: 3

palako
palako

Reputation: 3470

You have to place the code with the loop in a different thread and start the thread from your consumer.

private void consume() {
  Thread x = new Thread(new Runnable() {
    @Override
    public void run() {
      while(true) {
      ....
      }
   });
   x.start();
}

Upvotes: -2

Related Questions