/*
ESXX - The friendly ECMAscript/XML Application Server
Copyright (C) 2007-2015 Martin Blom <martin@blom.org>
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.esxx.util;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
public class ThreadSafeExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
private class TSFuture<V>
extends DelayedFutureTask<V> {
public TSFuture(Runnable r, V v, long time, long period) {
super(r, v, time, period);
}
public TSFuture(Callable<V> c, long time, long period) {
super(c, time, period);
}
@Override protected void reschedule() {
if (!isShutdown()) {
queueDelayed(this);
}
}
}
/** Create an unbounded thread pool */
public ThreadSafeExecutor(ThreadFactory tf) {
super(1, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
tf);
startDelayedWorker();
}
/** Create a fixed-size thread pool with CallerRunsPolicy */
public ThreadSafeExecutor(int worker_threads, ThreadFactory tf) {
// When using a bounded thread pool, SynchronousQueue and
// CallerRunsPolicy must be used in order to avoid deadlock. This
// is why we're not simply using ScheduledThreadPoolExecutor. See
// http://esxx.blogspot.com/2009/06/threadpoolexecutor-deadlocks.html
super(1, worker_threads + 1,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
tf, new ThreadPoolExecutor.CallerRunsPolicy());
startDelayedWorker();
}
// From interface ExecutorService
@Override public List<Runnable> shutdownNow() {
List<Runnable> result = super.shutdownNow();
result.addAll(Arrays.asList(delayQueue.toArray(new Runnable[0])));
delayQueue.clear();
return result;
}
// From interface ScheduledExecutorService
@Override public ScheduledFuture<?> schedule(Runnable r, long d, TimeUnit u) {
d = Math.max(0, d);
return queueDelayed(new TSFuture<Void>(r, null, System.nanoTime() + u.toNanos(d), 0));
}
@Override public <V> ScheduledFuture<V> schedule(Callable<V> c, long d, TimeUnit u) {
d = Math.max(0, d);
return queueDelayed(new TSFuture<V>(c, System.nanoTime() + u.toNanos(d), 0));
}
@Override public ScheduledFuture<?> scheduleAtFixedRate(Runnable r, long d, long p, TimeUnit u) {
d = Math.max(0, d);
if (p <= 0) {
throw new IllegalArgumentException();
}
return queueDelayed(new TSFuture<Void>(r, null,
System.nanoTime() + u.toNanos(d),
u.toNanos(p)));
}
@Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable r, long d, long p, TimeUnit u) {
d = Math.max(0, d);
if (p <= 0) {
throw new IllegalArgumentException();
}
return queueDelayed(new TSFuture<Void>(r, null,
System.nanoTime() + u.toNanos(d),
u.toNanos(-p))); // Use negative period to mark delay
}
private <V> TSFuture<V> queueDelayed(TSFuture<V> f) {
if (isShutdown() || !delayQueue.offer(f)) {
getRejectedExecutionHandler().rejectedExecution(f, this);
}
return f;
}
private void startDelayedWorker() {
execute(new Runnable() {
@SuppressWarnings("unchecked")
@Override public void run() {
try {
while (!isShutdown()) {
TSFuture<?> delayed = delayQueue.poll(1, TimeUnit.SECONDS);
if (delayed != null) {
execute(delayed);
}
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
catch (RejectedExecutionException ex) {
// Just exit
}
}
});
}
private DelayQueue<TSFuture<?>> delayQueue = new DelayQueue<TSFuture<?>>();
}