package com.linkedin.parseq; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import com.linkedin.parseq.Cancellable; /** * A wrapper around a delayed executor that provides better behavior for * cancellation. If a task is cancelled (or run), we forget the reference to * that task. Without this indirection it is possible that the underlying * scheduled executor will hold a reference to the task - even if it has been * cancelled - thus keeping a path to the task from a GC root. * * @author Chris Pettitt */ /* package private */ class IndirectDelayedExecutor implements DelayedExecutor { private final DelayedExecutor _executor; public IndirectDelayedExecutor(final DelayedExecutor executor) { _executor = executor; } @Override public Cancellable schedule(final long delay, final TimeUnit unit, final Runnable command) { final IndirectRunnable indirectRunnable = new IndirectRunnable(command); final Cancellable cancellable = _executor.schedule(delay, unit, indirectRunnable); return new IndirectCancellable(cancellable, indirectRunnable); } private static class IndirectRunnable implements Runnable { private AtomicReference<Runnable> _commandRef; public IndirectRunnable(final Runnable command) { _commandRef = new AtomicReference<Runnable>(command); } @Override public void run() { final Runnable runnable = _commandRef.get(); if (runnable != null && _commandRef.compareAndSet(runnable, null)) { runnable.run(); } } public boolean cancel() { final Runnable runnable = _commandRef.get(); return (runnable != null && _commandRef.compareAndSet(runnable, null)); } } private static class IndirectCancellable implements Cancellable { private final Cancellable _cancellable; private final IndirectRunnable _runnable; private IndirectCancellable(final Cancellable cancellable, final IndirectRunnable runnable) { _cancellable = cancellable; _runnable = runnable; } @Override public boolean cancel(final Exception reason) { return _runnable.cancel() && _cancellable.cancel(reason); } } }