Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple calls to CountDownLatch.await(int) with timeout

I use a CountDownLatch for waiting for a certain event from another component (running in a different thread). The following approach would fit the semantics of my software, but I'm not sure whether it works as a I expect:

mCountDownLatch.await(3000, TimeUnit.MILLISECONDS)
otherComponent.aStaticVolatileVariable = true;
mCountDownLatch.await(3500, TimeUnit.MILLISECONDS);
... <proceed with other stuff>

The scenario should be the following: I wait for 3 seconds, and if the latch is not counted down to 0, I notify the other component with that variable, and then I wait at most 3.5 seconds. If there is timeout again, then I don't care and will proceed with other operations.

Note: I know it doesn't look like that, but the above scenario is totally reasonable and valid in my software.

I did read the documentation of await(int,TimeUnit) and CountDownLatch, but I'm not a Java/Android expert, so I need confirmation. To me, all scenarios look valid:

  • If the first await is successful, then the other await will return immediately
  • If the first await times out, then the other await is still valid; therefore, if the other thread notices the static signal,the second await might return successfully
  • Both await calls time out (this is fine according to my software's semantics)

Am I using await(...) correctly? Can a second await(...) be used in the above way even if a previous await(...) on the same object timed out?

like image 826
Thomas Calc Avatar asked May 26 '12 23:05

Thomas Calc


1 Answers

If I understand your question correctly, this test proves that all your assumptions/requirements are true/met. (Run with JUnit and Hamcrest.) Note your code in the runCodeUnderTest() method, though it's interspersed with time recording and the timeouts are reduced by a factor of 10.

import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertThat;

public class CountdownLatchTest {
    static volatile boolean signal;
    CountDownLatch latch = new CountDownLatch(1);
    long elapsedTime;
    long[] wakeupTimes = new long[2];

    @Before
    public void setUp() throws Exception {
        signal = false;
    }

    @Test
    public void successfulCountDownDuringFirstAwait() throws Exception {
        countDownAfter(150);
        runCodeUnderTest();
        assertThat((double) elapsedTime, closeTo(150, 10));
        assertThat(wakeupTimeSeparation(), lessThan(10));
    }

    @Test
    public void successfulCountDownDuringSecondAwait() throws Exception {
        countDownAfter(450);
        runCodeUnderTest();
        assertThat((double) elapsedTime, closeTo(450, 10));
        assertThat((double) wakeupTimeSeparation(), closeTo(150, 10));
    }

    @Test
    public void neverCountDown() throws Exception {
        runCodeUnderTest();
        assertThat((double) elapsedTime, closeTo(650, 10));
        assertThat((double) wakeupTimeSeparation(), closeTo(350, 10));
    }

    @Test
    public void countDownAfterSecondTimeout() throws Exception {
        countDownAfter(1000);
        runCodeUnderTest();
        assertThat((double) elapsedTime, closeTo(650, 10));
        assertThat((double) wakeupTimeSeparation(), closeTo(350, 10));
    }

    @Test
    public void successfulCountDownFromSignalField() throws Exception {
        countDownAfterSignal();
        runCodeUnderTest();
        assertThat((double) elapsedTime, closeTo(300, 10));
    }

    private int wakeupTimeSeparation() {
        return (int) (wakeupTimes[1] - wakeupTimes[0]);
    }

    private void runCodeUnderTest() throws InterruptedException {
        long start = System.currentTimeMillis();
        latch.await(300, TimeUnit.MILLISECONDS);
        wakeupTimes[0] = System.currentTimeMillis();
        signal = true;
        latch.await(350, TimeUnit.MILLISECONDS);
        wakeupTimes[1] = System.currentTimeMillis();
        elapsedTime = wakeupTimes[1] - start;
    }

    private void countDownAfter(final long millis) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                sleep(millis);
                latch.countDown();
            }
        }).start();
    }

    private void countDownAfterSignal() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                boolean trying = true;
                while (trying) {
                    if (signal) {
                        latch.countDown();
                        trying = false;
                    }
                    sleep(5);
                }
            }
        }).start();
    }

    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw new IllegalStateException("Unexpected interrupt", e);
        }
    }
}
like image 81
Ryan Stewart Avatar answered Nov 06 '22 07:11

Ryan Stewart