4

I am iterating over an Iterator, where hasNext() will never return false. However, after a specified time (let's say 20 seconds), I want to stop iterating. The problem is that the next() method of the Iterator is blocking, but even so, after a specified time, I just need the iteration to stop.

Here is my example Iterable and Iterator to simulate my problem.

public class EndlessIterable implements Iterable<String> {
    static class EndlessIterator implements Iterator<String> {
         public boolean hasNext() { return true; }
         public String next() { 
             return "" + System.currentTimeMillis(); //in reality, this code does some long running task, so it's blocking
         }
    }
   public Iterator<String> iterator() { return new EndlessIterator(); }
}

Here is my code to test.

EndlessIterable iterable = new EndlessIterable();
for(String s : iterable) { System.out.println(s); }

I wanted to put code/logic into the Iterable class to create a Timer, so after the specified time is up, an exception will be thrown so as to stop the iteration.

public class EndlessIterable implements Iterable<String> {
    static class EndlessIterator implements Iterator<String> {
        public boolean hasNext() { return true; }
        public String next() { 
            try { Thread.sleep(2000); } catch(Exception) { } //just sleep for a while
            return "" + System.currentTimeMillis(); //in reality, this code does some long running task, so it's blocking
        }
    }
    static class ThrowableTimerTask extends TimerTask {
        private Timer timer;
        public ThrowableTimerTask(Timer timer) { this.timer = timer; }
        public void run() {
            this.timer.cancel();
            throw new RuntimeException("out of time!");
        }
    }
    private Timer timer;
    private long maxTime = 20000; //20 seconds
    public EndlessIterable(long maxTime) {
        this.maxTime = maxTime;
        this.timer = new Timer(true);
    }
    public Iterator<String> iterator() { 
        this.timer.schedule(new ThrowableTimerTask(this.timer), maxTime, maxTime);
        return new EndlessIterator();
    }
}

I then try to test this code as follows.

EndlessIterable iterable = new EndlessIterable(5000);
try {
    for(String s : iterable) { System.out.println(s); }
} catch(Exception) {
    System.out.println("exception detected: " + e.getMessage());
}
System.out.println("done");

What I noticed is that the RuntimeException is thrown after the time is up, however,

  • the for loop keeps going,
  • the catch block is never reached, and
  • i never reach the end of the code (printing done).

Any strategy, approach or design pattern to resolve this problem I've described?

Please note

  • in my actual code, i have no control over Iterator
  • i only have control over the Iterable and actual iteration
Akshay
  • 814
  • 6
  • 19
Jane Wayne
  • 8,205
  • 17
  • 75
  • 120
  • You are throwing an exception in a completely unrelated thread. Of course, this is not affecting your iteration. – Holger Jul 29 '14 at 18:11

4 Answers4

2

Your are using the wrong tool for your job. If you want to have a timeout for the operation you have to add the check to the operation. It’s recommended to separate the ordinary iterator logic from the timeout check which seems to fit to your remark that you can’t change the Iterator implementation. For this, use the decorator/delegation pattern:

// an iterator wrapping another one adding the timeout functionality
class TimeOutIterator<T> implements Iterator<T> {
  final Iterator<T> source;
  final long deadline;

  public TimeOutIterator(Iterator<T> dataSource, long timeout, TimeUnit unit) {
    source=dataSource;
    deadline=System.nanoTime()+unit.toNanos(timeout);
  }
  private void check() {
    if(System.nanoTime()-deadline >= 0)
      throw new RuntimeException("timeout reached");
  }
  public boolean hasNext() {
    check();
    return source.hasNext();
  }
  public T next() {
    check();
    return source.next();
  }
  public void remove() {
    check();
    source.remove();
  }
}

So you can implement your iterable as:

public class EndlessIterable implements Iterable<String> {
  static class EndlessIterator implements Iterator<String> {
   public boolean hasNext() { return true; }
   public String next() { 
     // dummy code illustrating the long running task
     try { Thread.sleep(2000); } catch(Exception e) { }
     return "" + System.currentTimeMillis();
   }
   public void remove() { throw new UnsupportedOperationException(); }
  }
  private long maxTime;
  private TimeUnit unit;

  public EndlessIterable(long maxTime, TimeUnit timeUnit) {
    this.maxTime = maxTime;
    this.unit = timeUnit;
  }
  public Iterator<String> iterator() { 
    return new TimeOutIterator<>(new EndlessIterator(), maxTime, unit);
  }
}

Then the testing code looks like:

// should timeout after five seconds
EndlessIterable iterable = new EndlessIterable(5, TimeUnit.SECONDS);
try {
 for(String s : iterable) { System.out.println(s); }
} catch(Exception e) {
  System.out.println("exception detected: " + e);
}
System.out.println("done");
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    This solution is great. However, there is still one minor problem: What if in this example, I tell the program to timeout after 5 seconds, but the first call to `next()` takes 10 seconds (i.e. change Thread.sleep(2000) to Thread.sleep(10000))? According to this code, then the first call to `next()` will return, and only the second call to `next()` will the iteration stop. Any advice on this resolving this? – Jane Wayne Jul 29 '14 at 18:48
  • Meaning, if the first call to `next()` takes 10 seconds, I already want the for loop to stop at 5 seconds, and not have to wait for it to return and then call `next()` again. – Jane Wayne Jul 29 '14 at 18:56
  • There is no general solution to this problem. The only way to end execution of arbitrary code is to use `Thread.stop()` which is deprecated for good reasons as it will leave to program in an undefined, potentially inconsistent state. The alternative is `Thread.interrupt()` which requires active support in the code being interrupted, e.g. the code must either poll the interrupted state regularly and end accordingly or it must use blocking operations which will throw `InterruptedException` when on thread interruption (and the code must react accordinly when an `InterruptedException` occurs. – Holger Jul 30 '14 at 08:03
  • So for the example code using `Thread.sleep()` interruption would work but I guess for your real use case things are a bit different. But even if the code supports interruption it might take some time before its execution reaches a point where it reacts on it. The only solution is not to rely on the exact fulfilling of the timeout. If you have an action that must take place timely, perform it in a different thread and trigger it by either the loop’s end or a timer whichever comes first. – Holger Jul 30 '14 at 08:08
1

There's no general way to stop all blocking operations. Some calls are interruptible, that is, they abort their operation with an error or partial results when another thread calls interrupt() on the working thread. Other times, there are hacks that will cause an operation to terminate; for example, if a thread is blocked reading a socket, another thread can close the socket.

However, in this case, you could perform the blocking operation needed to produce the next element in another thread. The iterator would be implemented to consume these elements from a BlockingQueue. This way, the iterating thread will return immediately When the timeout expires, rather than waiting an indefinite amount of extra time for the last element. The working thread might continue for a while, but it could check a flag before producing each element to determine whether to continue.

You have already accepted an answer, but if you are interested in this approach, let me know and I can sketch some code.

erickson
  • 265,237
  • 58
  • 395
  • 493
0

you can start this task in Thread and in another Thread you will start something like

Thread.sleep(20)

and then cancel the first thread

dpassy
  • 363
  • 1
  • 10
  • Could you please provide a complete example? – Jane Wayne Jul 29 '14 at 18:20
  • Cancelling threads has long since stopped being acceptable behavior because they can leave threads in invalid states that corrupt your application's data or crash the JVM. – Tim Jul 29 '14 at 18:40
0

Have a thread that sleeps for your time duration and then calls Thread.interrupt() on the thread that's doing the iteration, and then each time through the loop you can check for whether the thread isInterrupted() and exit the loop if it's ever true.

You can also use a Timer instead of calling Thread.sleep() inline; which approach is better for you will depend on what your calling thread is doing, etc.

Tim
  • 2,027
  • 15
  • 24