/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * A ScheduledThreadPoolExecutor which allows threads to time out after the keep alive time. With * the normal ScheduledThreadPoolExecutor, there is no way to configure it such that it only add * threads as needed. * * This executor is not very useful if you only want to have 1 thread. Use the * ScheduledThreadPoolExecutor in that case. This class with throw an exception if you try to * configure it with one thread. * * */ @SuppressWarnings("synthetic-access") public class ScheduledThreadPoolExecutorWithKeepAlive extends ThreadPoolExecutor implements ScheduledExecutorService { private final ScheduledThreadPoolExecutor timer; /** * @param corePoolSize * @param threadFactory */ public ScheduledThreadPoolExecutorWithKeepAlive(int corePoolSize, long keepAlive, TimeUnit timeUnit, ThreadFactory threadFactory) { super(0, corePoolSize - 1, keepAlive, timeUnit, new SynchronousQueue(), threadFactory, new BlockCallerPolicy()); timer = new ScheduledThreadPoolExecutor(1, threadFactory) { @Override protected void terminated() { super.terminated(); ScheduledThreadPoolExecutorWithKeepAlive.super.shutdown(); } }; } @Override public void execute(Runnable command) { timer.execute(new HandOffTask(command)); } @Override public Future submit(Callable task) { return schedule(task, 0, TimeUnit.NANOSECONDS); } @Override public Future submit(Runnable task, Object result) { return schedule(task, 0, TimeUnit.NANOSECONDS, result); } @Override public Future submit(Runnable task) { return schedule(task, 0, TimeUnit.NANOSECONDS); } public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { DelegatingScheduledFuture future = new DelegatingScheduledFuture(callable); ScheduledFuture timerFuture = timer.schedule(new HandOffTask(future), delay, unit); future.setDelegate(timerFuture); return future; } public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { return schedule(command, delay, unit, null); } private ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit, Object result) { DelegatingScheduledFuture future = new DelegatingScheduledFuture(command, result); ScheduledFuture timerFuture = timer.schedule(new HandOffTask(future), delay, unit); future.setDelegate(timerFuture); return future; } public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { DelegatingScheduledFuture future = new DelegatingScheduledFuture(command, null, true); ScheduledFuture timerFuture = timer.scheduleAtFixedRate(new HandOffTask(future), initialDelay, period, unit); future.setDelegate(timerFuture); return future; } public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { DelegatingScheduledFuture future = new DelegatingScheduledFuture(command, null, true); ScheduledFuture timerFuture = timer.scheduleWithFixedDelay(new HandOffTask(future), initialDelay, delay, unit); future.setDelegate(timerFuture); return future; } @Override public void shutdown() { // note - the timer has a "hook" which will shutdown our // worker pool once the timer is shutdown. timer.shutdown(); } /** * Shutdown the executor immediately, returning a list of tasks that haven't been run. Like * ScheduledThreadPoolExecutor, this returns a list of RunnableScheduledFuture objects, instead of * the actual tasks submitted. However, these Future objects are even less useful than the ones * returned by ScheduledThreadPoolExecutor. In particular, they don't match the future returned by * the {{@link #submit(Runnable)} method, and the run method won't do anything useful. This list * should only be used as a count of the number of tasks that didn't execute. * * @see ScheduledThreadPoolExecutor#shutdownNow() */ @Override public List shutdownNow() { List tasks = timer.shutdownNow(); super.shutdownNow(); return tasks; } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { long start = System.nanoTime(); if (!timer.awaitTermination(timeout, unit)) { return false; } long elapsed = System.nanoTime() - start; long remaining = unit.toNanos(timeout) - elapsed; if (remaining < 0) { return false; } return super.awaitTermination(remaining, TimeUnit.NANOSECONDS); } @Override public int getCorePoolSize() { return super.getCorePoolSize() + 1; } @Override public int getLargestPoolSize() { return super.getLargestPoolSize() + 1; } @Override public int getMaximumPoolSize() { return super.getMaximumPoolSize() + 1; } @Override public int getPoolSize() { return super.getPoolSize() + 1; } @Override public boolean isShutdown() { return timer.isShutdown(); } @Override public boolean isTerminated() { return super.isTerminated() && timer.isTerminated(); } // method that is in ScheduledThreadPoolExecutor that we should expose here public void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean b) { timer.setContinueExistingPeriodicTasksAfterShutdownPolicy(b); } // method that is in ScheduledThreadPoolExecutor that we should expose here public void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean b) { timer.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); } /** * A Runnable which we put in the timer which simply hands off the contain task for execution in * the thread pool when the timer fires. * */ private class HandOffTask implements Runnable { private final Runnable task; public HandOffTask(Runnable task) { this.task = task; } public void run() { try { ScheduledThreadPoolExecutorWithKeepAlive.super.execute(task); } catch (RejectedExecutionException e) { // do nothing, we'll only get this if we're shutting down. } } } /** * The future returned by the schedule* methods on this class. This future will not return a value * until the task has actually executed in the thread pool, but it allows us to cancel associated * timer task. * */ private static class DelegatingScheduledFuture<V> extends FutureTask<V> implements ScheduledFuture<V> { private ScheduledFuture<V> delegate; private final boolean periodic; public DelegatingScheduledFuture(Runnable runnable, V result) { this(runnable, result, false); } public DelegatingScheduledFuture(Callable<V> callable) { this(callable, false); } public DelegatingScheduledFuture(Runnable runnable, V result, boolean periodic) { super(runnable, result); this.periodic = periodic; } public DelegatingScheduledFuture(Callable<V> callable, boolean periodic) { super(callable); this.periodic = periodic; } @Override public void run() { if (periodic) { super.runAndReset(); } else { super.run(); } } public void setDelegate(ScheduledFuture<V> future) { this.delegate = future; } @Override public boolean cancel(boolean mayInterruptIfRunning) { delegate.cancel(true); return super.cancel(mayInterruptIfRunning); } public long getDelay(TimeUnit unit) { return delegate.getDelay(unit); } public int compareTo(Delayed o) { return delegate.compareTo(o); } @Override public boolean equals(Object o) { return delegate.equals(o); } @Override public int hashCode() { return delegate.hashCode(); } } /** * A RejectedExecutionHandler which causes the caller to block until there is space in the queue * for the task. */ protected static class BlockCallerPolicy implements RejectedExecutionHandler { public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { if (executor.isShutdown()) { throw new RejectedExecutionException("executor has been shutdown"); } else { try { executor.getQueue().put(r); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); RejectedExecutionException e = new RejectedExecutionException("interrupted"); e.initCause(ie); throw e; } } } } }