0

I am creating a system that will have multiple suite deployments and each deployment will have a queue of test suites. Since I want the test suites to run concurrently on their individual suite deployment, I need to add concurrency to the code. I have created a simplified version of the code I am using, but the concurrency portion doesn't work when I try to shut it down.

When the Runner.stopEverything() gets called, the result is that the queue gets emptied, and it waits for the threads to complete, but even when the tests all complete, the wait never finishes even with the notifyAll(). The result is that the process just sits there never ending. I go look at it in debug mode and the result is that all 3 threads show waiting.

Main:

public static void main(String args[]) throws Exception {
  Runner.queueTestSuites("SD1", Arrays.asList("A", "B", "C"));
  Runner.queueTestSuites("SD2", Arrays.asList("D", "E", "F"));
  Runner.queueTestSuites("SD3", Arrays.asList("G", "H", "I"));

  Thread.sleep(5000);

  System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~");
  Runner.stopEverything();
}

Runner:

public class Runner {
  private static Map<String, TestQueue> runnerQueueMap = new ConcurrentHashMap<>();

  public synchronized static void queueTestSuites(String suiteDeployment, List<String> testSuiteQueueAsJSON) throws Exception {
    TestQueue queue;
    if(runnerQueueMap.containsKey(suiteDeployment)) {
      queue = runnerQueueMap.get(suiteDeployment);
    } else {
      queue = new TestQueue(suiteDeployment);
    }
    for (int i = 0; i < testSuiteQueueAsJSON.size(); i++) {
      String name = testSuiteQueueAsJSON.get(i);
      queue.addToQueue(name);
    }
    runnerQueueMap.put(suiteDeployment,queue);
  }

  public synchronized static void stopEverything() throws InterruptedException {
    for (String s : runnerQueueMap.keySet()) {
      TestQueue q = runnerQueueMap.get(s);
      q.saveAndClearQueue();
    }

    for (String s : runnerQueueMap.keySet()) {
      TestQueue q = runnerQueueMap.get(s);
      q.waitForThread();
    }

    System.out.println("All done at " + new Date());
  }
}

TestQueue:

public class TestQueue {

  private Consumer consumer;
  private Thread consumerThread;
  private java.util.concurrent.BlockingQueue<String> queue;
  private String suiteDeployment;

  public TestQueue(String suiteDeployment) {
    this.suiteDeployment = suiteDeployment;
    queue = new ArrayBlockingQueue<>(100);
    startConsumer();
  }

  public void addToQueue(String testSuite) {
    try {
      queue.put(testSuite);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  public synchronized void waitForThread() {
    try {
      if (consumer.running.get()) {
        synchronized (consumerThread) {
          System.out.println("Waiting for " + consumerThread.getName());
          consumerThread.wait();
        }
      }
      System.out.println("Thread complete at " + new Date());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  public void saveAndClearQueue() {
    List<String> suiteNames = new ArrayList<>();
    for (String suite : queue) {
      suiteNames.add(suite);
    }
    queue.clear();
  }

  private void startConsumer() {
    consumer = new Consumer(queue,suiteDeployment);
    consumerThread = new Thread(consumer);
    consumerThread.start();
  }

  private class Consumer implements Runnable{
    private BlockingQueue<String> queue;
    private String suiteDeployment;
    public AtomicBoolean running;

    public Consumer(BlockingQueue<String> queue, String suiteDeployment){
      this.queue = queue;
      this.suiteDeployment = suiteDeployment;
      this.running = new AtomicBoolean(false);
    }

    @Override
    public void run() {
      try{
        while(!Thread.currentThread().isInterrupted()) {
          String testSuite = queue.take();
          this.running.set(true);
          new Test(testSuite, suiteDeployment).run();
          this.running.set(false);
        }
        notifyAll();
      }catch(Exception e) {
        e.printStackTrace();
      }
    }
  }
}

Test:

public class Test {

  String testSuite = "";
  String suiteDeployment = "";

  public Test(String testSuite, String suiteDeployment) {
    this.testSuite = testSuite;
    this.suiteDeployment = suiteDeployment;
  }

  public void run() {
    int time = new Random().nextInt() % 10000;
    time = Math.max(time, 3000);
    System.out.println("Test Started: " + testSuite + " on " + suiteDeployment + " at " + new Date() + " running for " + time + " on thread " + Thread.currentThread().getName());
    try {
      Thread.sleep(time);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Test Completed: " + testSuite + " on " + suiteDeployment + " at " + new Date());
  }
}
pandapirate
  • 19
  • 1
  • 4
  • `if (consumer.running.get()) { synchronized (consumerThread) { ` - what happens if `consumer.running` becomes false, and then the test thread calls `notifyAll`, when the main thread is just inside the `if` statement (but hasn't yet called `wait`)? – user253751 Feb 12 '15 at 23:49

1 Answers1

3

Inside run method of your consumer, you have a blocking call to queue.take() which means it will block until there is an item inside your queue. You run out of elements inside the queue eventually and all your thread are blocked by the queue.take() call waiting for more elements to become available to process.

Although your call is in a while loop where it check if the thread is interrupted, you actually never interrupt the threads so it never gets to the while loop evaluation & blocked at the call to queue.take()

So your threads stay in wait as they are waiting for input to become avilable inside your blocking queue

Also your saveAndClear method must lock on the correct object which is the queue itself, like below:

public void saveAndClearQueue() {
    List<String> suiteNames = new ArrayList<String>();
    synchronized (queue) {
        for (String suite : queue) {
            suiteNames.add(suite);
        }
        queue.clear();
    }
    System.out.println("Saved(not executed) : "+suiteNames);
}

And your waitForThread method should do sth like below:

public void waitForThread() {
    synchronized (consumerThread) {
        while (consumer.running.get()) {
            try {
                consumerThread.wait(100);
            } catch (InterruptedException e) {
                break;
            }
        }
    }

    if (!consumer.running.get()) {
        consumerThread.interrupt();
    }

    System.out.println("Thread complete at " + new Date());
}
fmucar
  • 14,361
  • 2
  • 45
  • 50