Java: set timeout on a certain block of code?

前端 未结 11 924
终归单人心
终归单人心 2020-11-27 03:45

Is it possible to force Java to throw an Exception after some block of code runs longer than acceptable?

相关标签:
11条回答
  • 2020-11-27 03:53

    I faced a similar kind of issue where my task was to push a message to SQS within a particular timeout. I used the trivial logic of executing it via another thread and waiting on its future object by specifying the timeout. This would give me a TIMEOUT exception in case of timeouts.

    final Future<ISendMessageResult> future = 
    timeoutHelperThreadPool.getExecutor().submit(() -> {
      return getQueueStore().sendMessage(request).get();
    });
    try {
      sendMessageResult = future.get(200, TimeUnit.MILLISECONDS);
      logger.info("SQS_PUSH_SUCCESSFUL");
      return true;
    
    } catch (final TimeoutException e) {
      logger.error("SQS_PUSH_TIMEOUT_EXCEPTION");
    }
    

    But there are cases where you can't stop the code being executed by another thread and you get true negatives in that case.

    For example - In my case, my request reached SQS and while the message was being pushed, my code logic encountered the specified timeout. Now in reality my message was pushed into the Queue but my main thread assumed it to be failed because of the TIMEOUT exception. This is a type of problem which can be avoided rather than being solved. Like in my case I avoided it by providing a timeout which would suffice in nearly all of the cases.

    If the code you want to interrupt is within you application and is not something like an API call then you can simply use

    future.cancel(true)
    

    However do remember that java docs says that it does guarantee that the execution will be blocked.

    "Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled,or could not be cancelled for some other reason. If successful,and this task has not started when cancel is called,this task should never run. If the task has already started,then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted inan attempt to stop the task."

    0 讨论(0)
  • 2020-11-27 03:57

    Yes, but its generally a very bad idea to force another thread to interrupt on a random line of code. You would only do this if you intend to shutdown the process.

    What you can do is to use Thread.interrupt() for a task after a certain amount of time. However, unless the code checks for this it won't work. An ExecutorService can make this easier with Future.cancel(true)

    Its much better for the code to time itself and stop when it needs to.

    0 讨论(0)
  • 2020-11-27 04:04

    There is a hacky way to do it.

    Set some boolean field to indicate whether the work was completed. Then before the block of code, set a timer to run a piece of code after your timeout. The timer will check if the block of code had finished executing, and if not, throw an exception. Otherwise it will do nothing.

    The end of the block of code should, of course, set the field to true to indicate the work was done.

    0 讨论(0)
  • 2020-11-27 04:06

    I created a very simple solution without using any frameworks or APIs. This looks more elegant and understandable. The class is called TimeoutBlock.

    public class TimeoutBlock {
    
     private final long timeoutMilliSeconds;
        private long timeoutInteval=100;
    
        public TimeoutBlock(long timeoutMilliSeconds){
            this.timeoutMilliSeconds=timeoutMilliSeconds;
        }
    
        public void addBlock(Runnable runnable) throws Throwable{
            long collectIntervals=0;
            Thread timeoutWorker=new Thread(runnable);
            timeoutWorker.start();
            do{ 
                if(collectIntervals>=this.timeoutMilliSeconds){
                    timeoutWorker.stop();
                    throw new Exception("<<<<<<<<<<****>>>>>>>>>>> Timeout Block Execution Time Exceeded In "+timeoutMilliSeconds+" Milli Seconds. Thread Block Terminated.");
                }
                collectIntervals+=timeoutInteval;           
                Thread.sleep(timeoutInteval);
    
            }while(timeoutWorker.isAlive());
            System.out.println("<<<<<<<<<<####>>>>>>>>>>> Timeout Block Executed Within "+collectIntervals+" Milli Seconds.");
        }
    
        /**
         * @return the timeoutInteval
         */
        public long getTimeoutInteval() {
            return timeoutInteval;
        }
    
        /**
         * @param timeoutInteval the timeoutInteval to set
         */
        public void setTimeoutInteval(long timeoutInteval) {
            this.timeoutInteval = timeoutInteval;
        }
    }
    

    example :

    try {
            TimeoutBlock timeoutBlock = new TimeoutBlock(10 * 60 * 1000);//set timeout in milliseconds
            Runnable block=new Runnable() {
    
                @Override
                public void run() {
                    //TO DO write block of code to execute
                }
            };
    
            timeoutBlock.addBlock(block);// execute the runnable block 
    
        } catch (Throwable e) {
            //catch the exception here . Which is block didn't execute within the time limit
        }
    

    This was so much useful for me when i had to connect to a FTP account. Then download and upload stuff. sometimes FTP connection hangs or totally breaks. This caused whole system to go down. and i needed a way to detect it and prevent it from happening . So i created this and used it. Works pretty well.

    0 讨论(0)
  • 2020-11-27 04:10

    I compiled some of the other answers into a single utility method:

    public class TimeLimitedCodeBlock {
    
      public static void runWithTimeout(final Runnable runnable, long timeout, TimeUnit timeUnit) throws Exception {
        runWithTimeout(new Callable<Object>() {
          @Override
          public Object call() throws Exception {
            runnable.run();
            return null;
          }
        }, timeout, timeUnit);
      }
    
      public static <T> T runWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
        final ExecutorService executor = Executors.newSingleThreadExecutor();
        final Future<T> future = executor.submit(callable);
        executor.shutdown(); // This does not cancel the already-scheduled task.
        try {
          return future.get(timeout, timeUnit);
        }
        catch (TimeoutException e) {
          //remove this if you do not want to cancel the job in progress
          //or set the argument to 'false' if you do not want to interrupt the thread
          future.cancel(true);
          throw e;
        }
        catch (ExecutionException e) {
          //unwrap the root cause
          Throwable t = e.getCause();
          if (t instanceof Error) {
            throw (Error) t;
          } else if (t instanceof Exception) {
            throw (Exception) t;
          } else {
            throw new IllegalStateException(t);
          }
        }
      }
    
    }
    

    Sample code making use of this utility method:

      public static void main(String[] args) throws Exception {
        final long startTime = System.currentTimeMillis();
        log(startTime, "calling runWithTimeout!");
        try {
          TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
            @Override
            public void run() {
              try {
                log(startTime, "starting sleep!");
                Thread.sleep(10000);
                log(startTime, "woke up!");
              }
              catch (InterruptedException e) {
                log(startTime, "was interrupted!");
              }
            }
          }, 5, TimeUnit.SECONDS);
        }
        catch (TimeoutException e) {
          log(startTime, "got timeout!");
        }
        log(startTime, "end of main method!");
      }
    
      private static void log(long startTime, String msg) {
        long elapsedSeconds = (System.currentTimeMillis() - startTime);
        System.out.format("%1$5sms [%2$16s] %3$s\n", elapsedSeconds, Thread.currentThread().getName(), msg);
      }
    

    Output from running the sample code on my machine:

        0ms [            main] calling runWithTimeout!
       13ms [ pool-1-thread-1] starting sleep!
     5015ms [            main] got timeout!
     5016ms [            main] end of main method!
     5015ms [ pool-1-thread-1] was interrupted!
    
    0 讨论(0)
  • 2020-11-27 04:10

    Instead of having the task in the new thread and the timer in the main thread, have the timer in the new thread and the task in the main thread:

    public static class TimeOut implements Runnable{
        public void run() {
            Thread.sleep(10000);
            if(taskComplete ==false) {
                System.out.println("Timed Out");
                return;
            }
            else {
                return;
            }
        }
    }
    public static boolean taskComplete = false;
    public static void main(String[] args) {
        TimeOut timeOut = new TimeOut();
        Thread timeOutThread = new Thread(timeOut);
        timeOutThread.start();
        //task starts here
        //task completed
        taskComplete =true;
        while(true) {//do all other stuff }
    }
    
    0 讨论(0)
提交回复
热议问题