Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Awaiting multiple instances of CountDownLatch in container

I wrote a class CountDownLatchContainer it has a await() method, that waits on all CountDownLatches. Everything is working fine.

public final class CountDownLatchContainer
{
  private final Set<CountDownLatch> countDowns;

  public CountDownLatchContainer(List<CountDownLatch> countDowns)
  {
    this.countDowns = new HashSet<CountDownLatch>(countDowns);
  }

  public void await() throws InterruptedException
  {
    for (CountDownLatch count : countDowns)
      count.await();
  }
}

For the sake of science and art (:D), I wanted to extend the functionality and add the public boolean await(long timeout, TimeUnit unit) from the CountDownLatch class.

I want each countdown latch to have the same timeout and the method overall just to block for the number of timeout TimeUnits. I have difficulties achieving that. What I have is:

  public boolean await(long timeout, TimeUnit unit) throws InterruptedException
  {
    boolean success = true;
    for (CountDownLatch count : countDowns)
      success &= count.await(timeout / countDowns.size(), unit);
    return success;
  }

So, the overall time out is taken care of, but each count down has just a percentage of the overall time.

So how can I give the single latches the same amount of time as specified in the method parameter without execeeding the total time?

Here a visualization. Thx to hexafraction for the ascii art inspiration.

|-----------------------------|
             Total
|-----------------------------|
  L1       
|-----------------------------|
  L2       
|-----------------------------|
like image 874
mike Avatar asked Jan 12 '23 10:01

mike


2 Answers

Work by awaiting each latch and then seeing how much time remains.

Start by awaiting the first latch for the original time. Note how long it actually took. Remaining time is set to the total minus time took. Now, await the next latch with a limit of the remaining time. Subtract actual. Continue until all latches are awaited or remaining time reaches 0(due to a latch timing out in its remaining time)

|-----------------------------|
              Total
|------|----------------------|
  L1       Remaining(1)
|------|---------|------------|
  L1     L2        Remaining(2)

and so on... await l1 for total, l2 for remaining(1), etc, etc

Another approach is to timeslice the operation into slices, such as 1 msec each. Loop the latches(marking them as used/removing them via an iterator of a mutable copy of the set could be good) and await for the slice length, until either all latches are released or the time is reached.

like image 77
nanofarad Avatar answered Jan 21 '23 15:01

nanofarad


Hexafraction's answer is correct, and far more sane than what I'm about to write, but you don't seem to like it for some reason. I would write this as a comment, on said answer, but its far too long, and needs some formatting. Go accept his answer, not this.

With his answer:

L1 gets 8 seconds and finishes at t1.

L2 gets 8 - t1 seconds, plus the t1 seconds while you were waiting on L1, and finishes at t2.

L3 gets 8 - t2 seconds, plus the t2 seconds while you were waiting on L1 and then L2.

...

L_N gets 8 - t_n-1 seconds, plus t_n-1 seconds waiting on the prior tasks.

Note that all of these add up to 8 seconds. Each latch has 8 seconds from the time that the call to await. In the end, either A) everything finished, and you return right after the last one did, or B) one or more tasks did not finish prior to the timeout.

It is logically the equivalent to the following, but IMHO, much cleaner code:

CountDownLatch[] latches;

public void await(final int timeout, final TimeUnit unit) {

  final CountDownLatch metaLatch = new CountDownLatch(latches.length);
  for (final int i = 0; i < latches.length; i++) {
     new Runnable() {
       public void run() {
         latches[i].await(timeout, unit);
         metaLatch.countDown();
       }
     }.start();
  }

  metaLatch.await(timeout, unit); 
}

You appear to be hung up on the fact that the timeout calls aren't exactly the same - but they can't be if you're going to do it serially, and they don't need to be. They are an implementation detail - each latch still gets 8s to run from the start of the call, which is what the contract of await(...) says. You requested an 8s timeout, and you get an 8s timeout. If all latches fired, it succeeds, if not, it fails, which is what you'd expect.

If you want to do it in parallel as above (plus a load of error handling which I've left out for brevity), you can, but quite honestly that is unnecessarily complicated for code that accomplishes the same thing as hexa's answer.

If none of this answers it, you might need to go back an edit to explain what you really mean, because I suspect you're leaving something out.

like image 37
James Avatar answered Jan 21 '23 14:01

James