Search code examples
javaterminate

How do I force terminate a thread in java with time limit?


I'm trying to find a way to set a time limit for running a block of code (force-terminate it when time is up) without modifying the internals of the block of code. Here's what I tried to do: I first copied the TimeLimitedCodeBlock class from this link: Java-how-to-set-timeout

import java.util.*;
import java.util.concurrent.*;
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) {
      future.cancel(true);
      throw e;
    }
    catch (ExecutionException e) {
      Throwable t = e.getCause();
      if (t instanceof Error) {
        throw (Error) t;
      } else if (t instanceof Exception) {
        throw (Exception) t;
      } else {
        throw new IllegalStateException(t);
      }
    }
  }

}

And here is what I ran using the class defined above:

public static void main(String [] args)
    {
            try{
                    TimeLimitedCodeBlock.runWithTimeout(new Runnable()
                                    {

                                    public void run()
                                    {
                                    try{
                                        while(true){}
                                    }catch(Exception e){}
                                    }},1,TimeUnit.SECONDS);
            }
            catch(Exception e){}
    }

And it's not terminating. How should I fix it so that it terminates?


Solution

  • Code snippet that I've used to do something similar:

     LOG.info("Time limited task started on monitored thread, with limit (" + limit + ")");
        final ZonedDateTime start = nowUTC();
        final Thread thread = new Thread(toRun);
        thread.setDaemon(true);
        final List<Throwable> exceptions = new ArrayList<>();
        thread.setUncaughtExceptionHandler((t, e) -> {
            exceptions.add(e);
        });
        thread.start();
    
        // Check and wait for completion.
        while (thread.isAlive()) {
            if (!isWithinLimit(start, nowUTC())) {
                LOG.error("Interrupting thread, did not complete before limit (" + limit + ")");
                try {
                    thread.interrupt();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                throw new TimeLimitExceedException("Execution limit of " + limit
                        + " exceeded. (Has been running since " + start + ")");
            }
            try {
                Thread.sleep(POLLING_PERIOD.toMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // If it failed because of an exception, we want to trigger this.
        if (!exceptions.isEmpty()) {
            final Throwable exception = exceptions.get(0);
            if (exception instanceof RuntimeException) {
                throw (RuntimeException) exception;
            } else {
                throw new RuntimeException(exception);
            }
        }
        final Duration runTime = Duration.between(start, nowUTC());
        LOG.info("Time limited task has completed in (" + runTime + ") vs limit of (" + limit
                + ").");
    

    TLDR: I just start whatever I'm running as a new thread that is set as a daemon (just in case it is the last thing running), then I get a reference to that and poll it, and call thread.interrupt() if it goes over the time limit.

    Other context & bells and whistles

    • This is part of a class that has some other state, like duration and what it is running
    • Also track some exceptions so that it can be spit out at the end if necessary