This blog has moved to Medium

Subscribe via email


Cancelor – a java task cancelation service

Lately I need to support task cancellation in a Java process I’m working on. The straightforward options I know to implement this are:

  1. Thread.interrupt() – the caller interrupts the worker thread (either directly or using Future.cancel()). Some say this is an erroneous approach, but I still haven’t figured out why. However, it is buggy on some recent versions on the JDK, and it is a bit fragile (what if the worker threads create worker threads that also need to be canceled?).
  2. Passing some object (AtomicBoolean?) down to every object you would like to support cancellation. These objects will check the value of this boolean, and should stop if it is false. They can pass the boolean to other objects / tasks. While this works, this boolean cannot be injected, and so must be manually passed along the call stack.

If you want the advantages of the second method, but don’t want to break IOC, here’s how:

First, the usage:

The listener object adds a dependency on ICancelor

public class Foo {
  public Foo(ICancelor cancelor) {
    this.cancelor = cancelor;
    ...
}

It then checks the cancellation state every now and then:

if (cancelor.wasTaskCanceled("TakeOverTheWorld"))
   return;

The top-level thread that wishes to cancel a task simply calls

cancelor.cancelTask("TakeOverTheWorld");

And whenever a task is started, you should call

cancelor.resetTask("TakeOverTheWorld");

I’ll admit using strings for task names is a bit ugly, but this is not a terrible price to pay, assuming you have a few core tasks you intend to support. All that remains is the cancellation service itself:

/**
 * A cancellation service.
 */
public interface ICancelor {
    /**
     * Resets a task to "Not canceled" state
     */
    void resetTask(String name);
 
    /**
     * Returns true iff the a cancelTask was called, and no resetTask was called afterwards.
     */
    boolean wasTaskCanceled(String name);
 
    /**
     * Cancel a task
     */
    void cancelTask(String name);
}
 
public class Cancelor implements ICancelor {
  private final ConcurrentHashMap tasks = new ConcurrentHashMap();
 
    public void resetTask(String name) {
        tasks.put(name, true);
    }
 
    public boolean wasTaskCanceled(String name) {
        Boolean value = tasks.get(name);
        return value != null & value;
    }
 
    public void cancelTask(String name) {
        tasks.put(name, false);
    }
}

Because we rely on task names, there is an assumption here that all classes that play in the cancellation game belong to the same task semantically. If a class is a common class that doesn’t belong to a single task or flow, this approach does not work – in fact, I cannot think of an approach that will work in this case with dependency injection. The common class has to accept the cancellation signal somehow, it must either get an boolean explicit and not from the IOC container, or must check its interrupted state (or some other thread-local state) itself. Any smart ideas on how to solve this problem?

5 Comments

  1. Tomer Gabel:

    Instead of using a string value and searching a global hashmap (potentially inefficient), there are several things you can do:

    * Ideally you’d use a volatile bool for this (since there’s only one state change — cancelled: false->true). I’m not sure it’s possible to pass such a variable byref to child threads, so you’ll probably need a reference type in there anyway.
    * Since that’s the case, you may want to use a single flag variable (AtomicBoolean), place it in an InheritableThreadLocal (http://java.sun.com/javase/6/docs/api/java/lang/InheritableThreadLocal.html) and expose it once when the primary task is constructed. This allows you to cancel the entire task tree with a single boolean write. I’m not sure how well this performs, but it does seem like an elegant solution to the problem and possibly worth investigating. There may also be issues when using a scheduler (since all tasks share a common thread pool) but I suppose there are ways around that as well.

  2. ripper234:

    Tomer, a boolean or AtomicBoolean is indeed the second other solution I discussed. The one downside is that sometimes you need to pass this boolean along a few call chains, which doesn’t play nice with IOC frameworks like Guice. It is true however that it’s a KISS solution that just works.

  3. Tomer Gabel:

    What’s IoC got to do with it? You can always inject an “ICancellationService” and expose the threadlocal as a getToken() method call or whatever. Using InheritableThreadLocal serves you the bother of having to manually route the flag between child tasks.

  4. ripper234:

    Fair enough. Alright, one more question: How would you use this if you’re not starting the thread directly but rather using an ExecutorService? You only get a Future back, I don’t believe you have direct access to the thread. I haven’t look at the code of Future yet, but you could probably extend Future to support this.

  5. ripper234:

    Strike that, Future is an interface, not a class. You’ll have to modify the code of the particular ExecutorService you’re using to return a Future that supports this, I think … does’t sound too nice.