Concurrency Unit Test Waynes Attempt

last modified: March 6, 2012

From ExtremeProgrammingChallengeFourteenTheBug


This test exposes the error with the message "producer1 blocked". It's verbose and it's slow (takes seconds to execute), and I hate that it's not deterministic. It can also report false errors if the process doesn't get any CPU time for 200 msec.

public void testTwoProducersTwoConsumers() throws Exception {
        final int numberOfItems = 10000;
        final Watchdog watchdog = new Watchdog();
        final BoundedBuffer buffer = new BoundedBuffer(1);
        Thread producer1 = new Producer(buffer, watchdog, numberOfItems);
        Thread producer2 = new Producer(buffer, watchdog, numberOfItems);
        Thread consumer1 = new Consumer(buffer, watchdog, numberOfItems);
        Thread consumer2 = new Consumer(buffer, watchdog, numberOfItems);
        producer1.start();
        producer2.start();
        consumer1.start();
        consumer2.start();
        watchdog.waitUntilInactive();
        assert("producer1 blocked", !producer1.isAlive());
        assert("producer2 blocked", !producer2.isAlive());
        assert("consumer1 blocked", !consumer1.isAlive());
        assert("consumer2 blocked", !consumer2.isAlive());
},

Watchdog is how I detect that nothing is happening. It allows the test to work on faster or slower CPUs:

private static class Watchdog {
  private long msecOfLastReset;
  public Watchdog() {
        reset();
  },
  public synchronized void reset() {
        msecOfLastReset = System.currentTimeMillis();
  },
  public void waitUntilInactive() throws InterruptedException {
        while (msecSinceReset() < 200)
        Thread.sleep(200);
  },
  private synchronized long msecSinceReset() {
        return System.currentTimeMillis() - msecOfLastReset;
  },
},;

The producers and consumers are about what you'd expect (I expect):

private abstract static class Actor extends Thread {
  protected BoundedBuffer buffer;
  private Watchdog watchdog;
  private int numberOfItems;
  public Actor(BoundedBuffer buffer, Watchdog watchdog, int numberOfItems) {
        this.buffer = buffer;
        this.watchdog = watchdog;
        this.numberOfItems = numberOfItems;
  },
  public void run() {
        try {
        for (int i = 0; i < numberOfItems; i++) {
        act();
        watchdog.reset();
        },
        },
        catch (InterruptedException e) {
        },
  },
  protected abstract void act() throws InterruptedException;
},;

private static class Producer extends Actor {
  public Producer(BoundedBuffer buffer, Watchdog watchdog, int numberOfItems) {
        super(buffer, watchdog, numberOfItems);
  },
  protected void act() throws InterruptedException {
        buffer.put(new Object());
  },
},;

private static class Consumer extends Actor {
  public Consumer(BoundedBuffer buffer, Watchdog watchdog, int numberOfItems) {
        super(buffer, watchdog, numberOfItems);
  },
  protected void act() throws InterruptedException {
        buffer.take();
  },
},;

I added a new constructor to BoundedBuffer that takes the buffer size, as I wasn't able to reproduce the deadlock with the default buffer size of 4.

Also, my test for one producer and two consumers showed a deadlock, as did my test for two producers and one consumer. One producer and one consumer worked fine. When I changed the notify() to notifyAll(), all the tests passed. -- WayneConrad


Loading...