When to use ScheduledThreadPoolExecutor's "removeOnCancel" Policy

Viewed 397

The Javadocs for Java's ScheduledThreadPoolExecutor state the following:

When a submitted task is cancelled before it is run, execution is suppressed. By default, such a cancelled task is not automatically removed from the work queue until its delay elapses. While this enables further inspection and monitoring, it may also cause unbounded retention of cancelled tasks. To avoid this, set setRemoveOnCancelPolicy(boolean) to true, which causes tasks to be immediately removed from the work queue at time of cancellation.

I interpret this as meaning that

  • by default, if you try to cancel a ScheduledFuture returned by the ScheduledThreadPoolExecutor when you "schedule" a task, the task will not be truly removed from the queue until the duration that we scheduled it has fully elapsed.
  • by setting ScheduledThreadPoolExecutor's removeOnCancelPolicy to true, we remove the task from the work queue immediately, rather than waiting for the scheduled duration until the task is taken off of the work queue.

Question

Are there any consequences to setting the removeOnCancelPolicy to true?

I'm considering doing it in order to make sure that the work queue doesn't get too large and take up too much memory, but I'm confused as to why this policy isn't on by default.

2 Answers

@hc_dev's answer is great and explains many of the consequences of setting removeOnCancelPolicy to true, but there's one more consequence of setting that value to true - the concurrency of the blocking queue within your ScheduledExecutorService may be affected.

By default, the ScheduledExecutorService created by the Executors.newScheduledThreadPool factory method uses a ScheduledThreadPoolExecutor.DelayedWorkQueue as its work queue. This work queue consists of one array contained within it, guarded by one lock. This means that while one thread is removeing tasks from the ScheduledThreadPoolExecutor.DelayedWorkQueue, other threads will have to wait in order to push or pop anything from the work queue, each time that a task is cancelled. So, setting a removeOnCancelPolicy will raise the contention for the locks on the blocking queue and lower the concurrency of the queue.

The Javadocs have this to say about ScheduledThreadPoolExecutor.DelayedWorkQueue's performance with it's remove method:

A DelayedWorkQueue is based on a heap-based data structure like those in DelayQueue and PriorityQueue, except that every ScheduledFutureTask also records its index into the heap array. This eliminates the need to find a task upon cancellation, greatly speeding up removal (down from O(n) to O(log n)), and reducing garbage retention that would otherwise occur by waiting for the element to rise to top before clearing.

So, the performance of the remove method is quite good, and scales well with many entries in the queue, but this is something to keep in mind.

Cancelling

A task is cancelled by invoking method Future.cancel(boolean).

So suppose you scheduled a task by schedule which implies a delay on the queue until execution starts:

Creates and executes a one-shot action that becomes enabled after the given delay.

Runnable task = new Callable<String>() {
    public String call() { return "Hello Future World!"; }
};

Future<String> future = executor.schedule(task, 50L, TimeUnit.SECONDS);
future.cancel(true);

The task is pushed to the work queue of this executor and waits there for execution (at least the specified delay of 50L, SECONDS). During this duration, you can cancel it. But depending on the removeOnCancelPolicy policy it either remains on the queue until execution would start (false) or it will be removed immediately (true).

Purge (related design considerations)

  • What happens if we cancel a ScheduledFutureTask?

The behaviour depends on policy removeOnCancel of class ScheduledThreadPoolExecutor - see implementation of this inner-class method ScheduledFutureTask.cancel

public boolean cancel(boolean mayInterruptIfRunning) {
    boolean cancelled = super.cancel(mayInterruptIfRunning);
    if (cancelled && removeOnCancel && heapIndex >= 0)
        remove(this);
    return cancelled;
}
  • What happens if a task was only cancelled, but not removed (by policy)?

The design considers this. See the parent class ThreadPoolExecutor's methods remove(runnable) and especially purge():

Tries to remove from the work queue all Future tasks that have been cancelled. This method can be useful as a storage reclamation operation, that has no other impact on functionality. Cancelled tasks are never executed, but may accumulate in work queues until worker threads can actively remove them. Invoking this method instead tries to remove them now. However, this method may fail to remove tasks in the presence of interference by other threads.

Queues as scarce resource

A queue, especially a blocking-queue is somehow limited (e.g. in capacity: holds max items) why you should always strive to keep them short, not too much work load queued up, process tasks as fast as possible.

Thus the docs warned about removeOnCancelPolicy set false:

may also cause unbounded retention of cancelled tasks

So the policy has impact on the queue, its fill-level (max capacity), possible rejection of further submitted tasks, etc.

You could experiment the behaviour of this policy, by inspecting the underlying BlockingQueue of your executor: its size, the elements of Future on it. Their isCancelled state, etc.

Use case: view cancelled tasks and beyond

If the queue retains cancelled tasks then this allows to put the user in control (Usability principle), re-/un-do effort can be reduced, rescheduling can be a useful feature (as following example illustrates).

Cancelling a task

Suppose you have a report-scheduler with a some UI. When a user schedules a report then the report will be submitted (with some delay) to your executor/queue.

Given the user scheduled a report and a second later recognizes that it got the wrong parameters. Because they will cause the report to run very long, they immediately cancel the scheduled report. Lucky they were, that it wasn't too late.

Now: This means additional effort, because the wrong parameters must be corrected or reentered and the report scheduled again.

Rescheduling a task

Instead of entering all the parameters again, they want to reschedule the existing canceled one, and just modify a few parameters before clicking on "Submit" or "Schedule".

Not forgotten, forgiven!

If the policy removeOnCancelPolicy is set to false

this enables further inspection and monitoring

A view lists all tasks, filtered by status (canceled, scheduled, running, completed, etc.), so that the canceled one can be selected and its details (schedule, parameters) can be viewed. Additionally the action "Reschedule" makes it easy to submit it again.

Related