1
votes

I have the following code whose purpose is to increment a prometheus counter if periodic calls stop coming to messageReceived():

...

    private static final int tenMinutes = 10 * 60 * 1000;
    private Timer timer = new Timer();
    private boolean newTimer = false;

...
    public void messageReceived() {

        timer.cancel();
        timer = new Timer();
        newTimer = true;

        TimerTask action = new TimerTask() {
            public void run() {
                if (!newTimer)
                    counter.increment();
                else
                    newTimer = false;
            }

        };

        timer.schedule(action, tenMinutes, tenMinutes);

    }

  ...

The goal is to set a timer that will only fire an action if a new event is not received. Every time messageReceived() is called before ten minutes have passed, the timer should be cancelled so it will not fire.

What I am seeing happen is pretty much exactly every ten minutes the action fires, even though messageReceived is called more than once per minute.

MessageReceived is called from a service so its not called on the same thread every time, but messageReceived is inside a singleton. I am not sure, but I would think that if multithreading was the problem, I would see many firings of "action" and not just one every 10 minutes.

1
If your problem is really just about timers and timer tasks, please simplify the code so as to only reproduce your problem without the additional dependencies. And please also explain what the expected result is and what happens instead, because I don't get it. You see the timer task fire every ten minutes, you say. Isn't that what you want? Your question is not getting any attention because it is incomprehensible. So just explain it better.kriegaex
I think it has to do with multi-threading issues. Are you sure the "messageReceived()" method is called from the same thread every time? If not you need to make sure the Timer instance is syncronized along all threads. Furhtermore, I would strongly advice to use ExecutorService over Timer, info: (baeldung.com/java-executor-service-tutorial)SnowmanXL
@SnowmanXL .. please see my comment about multithreading and messageReceived. Would you agree that I should see many firings of action if multiple threads was the issue? I will look into ExecutorService.chrismead
@kriegaex .. please note that I have simplified.chrismead
Cutting off existing code, leaving behind just a skeleton without any context which I cannot even compile and run, is not what I would call simplification. It rather is obfuscation. I had to copy your original code from a historic version of this question in order to produce an MCVE for my answer below. Please do read the article about MCVE and learn why it makes sense to produce one for each question. That way in the future you will get quicker and better answers. Thank you. 😀kriegaex

1 Answers

0
votes

I think you do have a multi-threading problem, just like SnowmanXL said. Here is a simple MCVE reproducing the problem:

import java.text.SimpleDateFormat;
import java.util.*;

class MiscellaneousMonitor {
  private static SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");

  private boolean isRunning;
  private Counter counter;
  private static final int tenMinutes = /*10 * 60 **/ 1000;
  private Timer timer = new Timer();
  private boolean newTimer = false;

  static class Counter {
    private int count = 0;

    public /*synchronized*/ void increment() {
      count++;
    }
  }

  public /*synchronized*/ void start() {
    counter = new Counter();
    isRunning = true;
  }

  public /*synchronized*/ void messageReceived() {
    timer.cancel();
    timer = new Timer();
    newTimer = true;
    TimerTask action = new TimerTask() {
      public void run() {
        System.out.println(dateFormat.format(new Date()) + " Timer task running: " + this);
        if (!newTimer)
          counter.increment();
        else
          newTimer = false;
      }
    };
    timer.schedule(action, tenMinutes, tenMinutes);
  }

  public /*synchronized*/ void stop() {
    timer.cancel();
    isRunning = false;
  }

  public /*synchronized*/ boolean isRunning() {
    return isRunning;
  }

  public static void main(String[] args) throws InterruptedException {
    MiscellaneousMonitor monitor = new MiscellaneousMonitor();
    monitor.start();
    Queue<Thread> threads = new LinkedList<>();
    for (int t = 0; t < 10; t++) {
      Thread thread = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
          try { Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); }
          monitor.messageReceived();
        }
        try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); }
      });
      thread.start();
      threads.add(thread);
    }
    while (!threads.isEmpty()) {
      threads.poll().join();
    }
    monitor.stop();
  }
}

The console log will look something like this:

Exception in thread "Thread-4" java.lang.IllegalStateException: Timer already cancelled.
    at java.base/java.util.Timer.sched(Timer.java:398)
    at java.base/java.util.Timer.schedule(Timer.java:249)
    at MiscellaneousMonitor.messageReceived(scratch_3.java:39)
    at MiscellaneousMonitor.lambda$main$0(scratch_3.java:59)
    at java.base/java.lang.Thread.run(Thread.java:832)
09:25:58.147 Timer task running: MiscellaneousMonitor$1@1ce7fd7d
09:25:58.142 Timer task running: MiscellaneousMonitor$1@7ba42a49
09:25:58.147 Timer task running: MiscellaneousMonitor$1@493cb0eb
09:25:58.147 Timer task running: MiscellaneousMonitor$1@6f9a3afe
09:25:58.148 Timer task running: MiscellaneousMonitor$1@1d86f308
Exception in thread "Thread-9" java.lang.IllegalStateException: Timer already cancelled.
    at java.base/java.util.Timer.sched(Timer.java:398)
    at java.base/java.util.Timer.schedule(Timer.java:249)
    at MiscellaneousMonitor.messageReceived(scratch_3.java:39)
    at MiscellaneousMonitor.lambda$main$0(scratch_3.java:59)
    at java.base/java.lang.Thread.run(Thread.java:832)
09:25:58.445 Timer task running: MiscellaneousMonitor$1@53c65632
09:25:58.445 Timer task running: MiscellaneousMonitor$1@6ce24daa
09:25:58.445 Timer task running: MiscellaneousMonitor$1@784b861f
09:25:58.447 Timer task running: MiscellaneousMonitor$1@783528c9
09:25:58.447 Timer task running: MiscellaneousMonitor$1@2cc4944f
09:25:58.597 Timer task running: MiscellaneousMonitor$1@711e91d9
09:25:58.597 Timer task running: MiscellaneousMonitor$1@19ddcb88
09:25:58.597 Timer task running: MiscellaneousMonitor$1@5fbdc1a8
(...)

Sometimes you see the execeptions, sometimes not, depending on the timing when you run the program. But even if you do not see any exceptions, multiple timer tasks - MiscellaneousMonitor$1 is the internal name of the anonymous TimerTask instance - will log forever and never be cancelled, which is why the program continues to run forever until you kill it, despite you calling join() on all running tasks. But there are still rogue TimerTasks.

Now if you uncomment all synchronized keywords where I put them in the code, your console log will change to the expected

09:31:44.880 Timer task running: MiscellaneousMonitor$1@4f963263

and the program will terminate.

P.S.: You maybe could synchronise on smaller sections of code instead of on whole methods, I did not analyse that. I just showed you the basic problem of thread unsafety with your singleton which is accessed by multiple other threads, like you said.