Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Callback when a periodic task is cancelled and done

Tags:

java

I have two tasks: The first task (work) is reoccurring and the second task (cleanup) is releases some resources. The cleanup task should be run exactly once after the reoccurring work task has completed and will not be run again.

My first instinct was something like this:

ScheduledExecutorService service = ...;
ScheduledFuture<?> future = service.scheduleAtFixedRate(work, ...);

// other stuff happens

future.cancel(false);
cleanup.run();

The problem here is that cancel() returns immediately. So if work happens to be running, then cleanup will overlap it.

Ideally I would use something like Guava's Futures.addCallback(ListenableFuture future, FutureCallback callback). (Guava 15 may have something like that).

In the meantime, how can fire a callback when future is cancelled and work no longer running?

like image 493
Drew Avatar asked Nov 03 '22 01:11

Drew


2 Answers

This is the solution that I've come up with. It seems to be pretty simple, but I still assume there's a more common and/or elegant solution out there. I'd really like to see one in a library like Guava...

First I create a wrapper to impose mutual exclusion on my Runnables:

private static final class SynchronizedRunnable implements Runnable {
    private final Object monitor;
    private final Runnable delegate;

    private SynchronizedRunnable(Object monitor, Runnable delegate) {
        this.monitor = monitor;
        this.delegate = delegate;
    }

    @Override
    public void run() {
        synchronized (monitor) {
            delegate.run();
        }
    }
}

Then I create a wrapper to fire my callback on successful invokations of cancel:

private static final class FutureWithCancelCallback<V> extends ForwardingFuture.SimpleForwardingFuture<V> {

    private final Runnable callback;

    private FutureWithCancelCallback(Future<V> delegate, Runnable callback) {
        super(delegate);
        this.callback = callback;
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
            boolean cancelled = super.cancel(mayInterruptIfRunning);
            if (cancelled) {
                callback.run();
            }
            return cancelled;
    }
}

Then I roll it all together in my own method:

private Future<?> scheduleWithFixedDelayAndCallback(ScheduledExecutorService service, Runnable work, long initialDelay, long delay, TimeUnit unit, Runnable cleanup) {

    Object monitor = new Object();

    Runnable monitoredWork = new SynchronizedRunnable(monitor, work);

    Runnable monitoredCleanup = new SynchronizedRunnable(monitor, cleanup);

    Future<?> rawFuture = service.scheduleAtFixedRate(monitoredWork, initialDelay, delay, unit);

    Future<?> wrappedFuture = new FutureWithCancelCallback(rawFuture, monitoredCleanup);

    return wrappedFuture;
}
like image 129
Drew Avatar answered Nov 12 '22 22:11

Drew


I'll give it another shot then. Either you may enhance the command or you may wrap the executed Runnable/Callable. Look at this:

public static class RunnableWrapper implements Runnable {

    private final Runnable original;
    private final Lock lock = new ReentrantLock();

    public RunnableWrapper(Runnable original) {
        this.original = original;
    }

    public void run() {
        lock.lock();
        try {
            this.original.run();
        } finally {
            lock.unlock();
        }
    }

    public void awaitTermination() {
        lock.lock();
        try {
        } finally {
            lock.unlock();
        }
    }

}

So you can change your code to

ScheduledExecutorService service = ...;
RunnableWrapper wrapper = new RunnableWrapper(work);
ScheduledFuture<?> future = service.scheduleAtFixedRate(wrapper, ...);

// other stuff happens

future.cancel(false);
wrapper.awaitTermination();
cleanup.run();

After calling cancel, either work is no longer running and awaitTermination() returns immediately, or it is running and awaitTermination() blocks until it's done.

like image 44
skirsch Avatar answered Nov 12 '22 21:11

skirsch