Debugging Event Driven Multithreaded Java Code

So here’s the scenario: you have few potentially time consuming work need to be done. They need to be executed in the order of submission, but only one work at a time can run. Simple solution is to use single thread executor.

First let’s define the Job class. This is a simple class implementing Runnable, when the job runs / stops it will print to console. A random sleep between 1 to 5 seconds is introduced to simulate some reality.

public class Job implements Runnable {

  private String id;

  public Job(String id) {
    this.id = id;
  }
  
  @Override
  public void run() {
    System.out.println("Job " + id + " started");
    
    long duration = (new Random(System.currentTimeMillis()).nextInt(5) + 1) * 1000;
    try {
      Thread.sleep(duration);
    } catch (InterruptedException e) {
      System.out.println("Job " + id + " interrupted");
      e.printStackTrace();
    }
    
    System.out.println("Job " + id + " completed");
  }

}

To run the job I have a JobRunner class that setups the ExecutorService. Just call submit(job) and the job will be queued and run when the worker thread is free.

public class JobRunner {

  private ExecutorService executor = Executors.newSingleThreadExecutor();

  public void enqueue(Job job) {
    executor.submit(job);
  }

}

But a debugging problem starts to appear. It is now obscure where does the code goes after calling submit()? Which thread runs it? Unless the programmer put a big comment informing it is actually a Runnable this could be very hard to guess.

Further Problems with Observer Pattern

Another common patterns often used is the observer pattern. The idea is you want specific objects to “react” when a particular event occured.

For example, let’s do two observers: OddJobObservers and EvenJobObservers. When an odd/even job completed the corresponding observer will print to console.

public class OddJobObserver implements Observer {

  @Override
  public void update(Observable arg0, Object arg1) {
    int id = (int) arg1;
    if(id % 2 == 1)
      System.out.println("Odd job " + id + " completed");
  }

}
public class EvenJobObserver implements Observer {

  @Override
  public void update(Observable o, Object arg) {
    int id = (int) arg;
    if(id % 2 == 0)
      System.out.println("Even job " + id + " finished");
  }

}

For this purpose we’ll refactor JobRunner to extend Observable so we can add the observers into it. The observer instances are created and registered on the constructor.

Each time a job is created it will also have reference back to JobRunner. We’ll also add a method jobFinished(int id) for the job to call when it’s done running.

public class JobRunner extends Observable {

  private ExecutorService executor = Executors.newSingleThreadExecutor();

  public JobRunner() {
    addObserver(new OddJobObserver());
    addObserver(new EvenJobObserver());
  }

  public void enqueue(Job job) {
    job.setJobRunner(this);
    executor.submit(job);
  }

  /**
   * This will be invoked by the Job's run method in the worker thread
   * to indicate the runner that this particular job is finished
   */
  public void jobFinished(int id) {
    setChanged();
    notifyObservers();
  }
}

And on the Job class once it’s finshed running we’ll notify the observers

public class Job implements Runnable {

  // ...
  
  @Override
  public void run() {
    System.out.println("Job " + id + " started");
    
    // ...

    System.out.println("Job " + id + " completed");
    jobRunner.jobFinished(id);
  }

}

Consider debugging this code where you’re at the end of run() method which takes you to jobFinished(). It is now even more obscure because notifyObservers() is a Java API method, not your own code. Which code gets executed next?

Unless you did a good job on commenting, it’s very hard for someone reading your code to understand that at that point odd jobs will be observed by OddJobObserver and even jobs / EvenJobObserver.

The problem gets much worse with production client / server application with 50+ observers handling different scenario.

One approach is probably to put breakpoints on every single observers and see which one is actually “interested” in our event.

I hope this highlights how important properly commenting your code is (especially for your poor colleague who has to fix your code sometime in the future).

Advertisements