Using ScheduledExecutorService drastically slows down execution

55 Views Asked by At

I need to limit the execution time of some methods so am using ScheduledExecutorService not to add concurrency but to enforce a timeout.

public class Task {
    private static final ScheduledExecutorService scheduler = Executors
            .newScheduledThreadPool(1);

    public abstract Callable<Result> getCallable(Target target);

    public Result processTarget(Target target) {
        try {
            ScheduledFuture<List<Result>> future = scheduler.schedule(
                        getCallable(target), 10, TimeUnit.SECONDS);
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}  

While this code works, the performance is unacceptable. For some concrete subclasses, the call to future.get() takes 10 seconds, even if the call to the Callable function takes only milliseconds when called directly. Increasing the thread pool size makes no difference.

For example, one concrete subclass returns a Callable that runs PMD programmatically.

public class PmdGrader extends Task {
    @Override
    public Callable<Result> getCallable(final Target target) {
        return () -> {
            try (PmdAnalysis analysis = createAnalysis()) {
                analysis.files().addFileOrDirectory(target.toPath());
                Report report = analysis.performAnalysisAndCollectReport();
                return produceResults(report);
            }
        };
    }

Before adding ScheduledExecutorService (i.e., when doing the work in the main thread), unit tests involving PmdGrader took 13-696 ms each. After adding concurrency, some tests took the same amount of time, while others took approximately 10 seconds more than their original times. If I increase the timeout from 10 seconds to 15 seconds, those tests take an additional 5 seconds. In other words, instead of being a maximum time (as I intended), the timeout is acting as a minimum time.

Questions

  1. Is there a way to use ScheduledExecutorService without this huge performance hit.
  2. Is there a better library (whether included with Java or external)?
1

There are 1 best solutions below

0
Ellen Spertus On

As user16320675 helpfully pointed out, I misunderstood the API: The argument to ScheduledExecutorService.schedule() specifies when to start the task, not when to end it.

The corrected code uses ExecutorService.submit() and Future.get(long, TimeUnit) as shown:

public class Task {
    ExecutorService executor = Executors.newSingleThreadExecutor();

    public abstract Callable<Result> getCallable(Target target);

    public Result processTarget(Target target) {
        try {
            Future<List<Result>> future = executor.submit(getCallable(target));
            return future.get(10, TimeUnit.SECONDS);
        } catch (InterruptedException | TimeoutException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}