/* * Copyright 2012 Jason Miller * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jj.execution; import java.lang.ref.WeakReference; import java.time.Clock; import java.util.concurrent.BlockingQueue; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * <p> * supports a simple "run every x time units" and "run after x time units" approach to * scheduling. the java stuff is nice, but works too hard for my needs and makes some * other things too sloppy * * <p> * If the runnable submitted to this executor throws an exception it will be silently * discarded, so make sure you do all your handling below this. Presuming you use this * executor through the {@link TaskRunner} this is handled for you. If you use this * executor in an ad-hoc fashion, then it is on you. * @author jason * */ public abstract class DelayedExecutor extends ThreadPoolExecutor { public class CancelKey { private final WeakReference<DelayedRunnable> runnable; private volatile boolean canceled = false; private CancelKey(DelayedRunnable runnable) { this.runnable = new WeakReference<>(runnable); } public void cancel() { DelayedRunnable r = runnable.get(); if (r != null) { canceled = true; r.canceled.set(true); delayedTasks.remove(r); } } public boolean canceled() { return canceled; } } private class DelayedRunnable implements Runnable, Delayed { private final Runnable runnable; private final long delay; private final TimeUnit timeUnit; private final long start = clock.millis(); private final AtomicBoolean canceled = new AtomicBoolean(); protected DelayedRunnable(Runnable runnable, long delay, TimeUnit timeUnit) { this.runnable = runnable; this.delay = delay; this.timeUnit = timeUnit; } @Override public void run() { if (!canceled.get()) { runnable.run(); } } @Override public int compareTo(Delayed o) { long x = getDelay(TimeUnit.MILLISECONDS); long y = o.getDelay(TimeUnit.MILLISECONDS); return (x < y) ? -1 : ((x == y) ? 0 : 1); } @Override public long getDelay(TimeUnit unit) { return unit.convert((start + delay) - clock.millis(), timeUnit); } } protected final Clock clock; protected final JJThreadFactory threadFactory; private final DelayQueue<DelayedRunnable> delayedTasks = new DelayQueue<>(); private final Runnable scheduler = () -> { Thread.currentThread().setName(schedulerThreadName()); try { while (true) { DelayedRunnable runnable = delayedTasks.take(); if (asynchronousScheduling()) { submit(runnable); } else { try { runnable.run(); } catch (Throwable t) {} } } } catch (InterruptedException e) { // just die } }; protected DelayedExecutor( final Clock clock, final int corePoolSize, final int maxPoolSize, final int timeOut, final TimeUnit timeOutUnit, final BlockingQueue<Runnable> workQueue, final JJThreadFactory threadFactory, final JJRejectedExecutionHandler handler ) { super( corePoolSize, maxPoolSize, timeOut, timeOutUnit, workQueue, threadFactory, handler ); this.clock = clock; this.threadFactory = threadFactory; submit(scheduler); } /** * Name your scheduler thread something intelligent for easier debugging * @return */ protected abstract String schedulerThreadName(); /** * By default, this executor will run a scheduler thread and dispatch the actual tasks * back into the executor. If you want a single-thread executor, override this and return * false, and the tasks will execute inline. * @return */ protected boolean asynchronousScheduling() { return true; } public CancelKey submit(Runnable runnable, long delay, TimeUnit timeUnit) { DelayedRunnable delayed = new DelayedRunnable(runnable, delay, timeUnit); delayedTasks.add(delayed); return new CancelKey(delayed); } }