| /* | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code 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 | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* | |
| * This file is available under and governed by the GNU General Public | |
| * License version 2 only, as published by the Free Software Foundation. | |
| * However, the following notice accompanied the original version of this | |
| * file: | |
| * | |
| * Written by Doug Lea with assistance from members of JCP JSR-166 | |
| * Expert Group and released to the public domain, as explained at | |
| * http://creativecommons.org/publicdomain/zero/1.0/ | |
| */ | |
| package java.util.concurrent; | |
| import static java.util.concurrent.TimeUnit.MILLISECONDS; | |
| import static java.util.concurrent.TimeUnit.NANOSECONDS; | |
| import java.util.AbstractQueue; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.NoSuchElementException; | |
| import java.util.Objects; | |
| import java.util.concurrent.atomic.AtomicLong; | |
| import java.util.concurrent.locks.Condition; | |
| import java.util.concurrent.locks.ReentrantLock; | |
| /** | |
| * A {@link ThreadPoolExecutor} that can additionally schedule | |
| * commands to run after a given delay, or to execute periodically. | |
| * This class is preferable to {@link java.util.Timer} when multiple | |
| * worker threads are needed, or when the additional flexibility or | |
| * capabilities of {@link ThreadPoolExecutor} (which this class | |
| * extends) are required. | |
| * | |
| * <p>Delayed tasks execute no sooner than they are enabled, but | |
| * without any real-time guarantees about when, after they are | |
| * enabled, they will commence. Tasks scheduled for exactly the same | |
| * execution time are enabled in first-in-first-out (FIFO) order of | |
| * submission. | |
| * | |
| * <p>When a submitted task is cancelled before it is run, execution | |
| * is suppressed. By default, such a cancelled task is not | |
| * automatically removed from the work queue until its delay elapses. | |
| * While this enables further inspection and monitoring, it may also | |
| * cause unbounded retention of cancelled tasks. To avoid this, use | |
| * {@link #setRemoveOnCancelPolicy} to cause tasks to be immediately | |
| * removed from the work queue at time of cancellation. | |
| * | |
| * <p>Successive executions of a periodic task scheduled via | |
| * {@link #scheduleAtFixedRate scheduleAtFixedRate} or | |
| * {@link #scheduleWithFixedDelay scheduleWithFixedDelay} | |
| * do not overlap. While different executions may be performed by | |
| * different threads, the effects of prior executions | |
| * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a> | |
| * those of subsequent ones. | |
| * | |
| * <p>While this class inherits from {@link ThreadPoolExecutor}, a few | |
| * of the inherited tuning methods are not useful for it. In | |
| * particular, because it acts as a fixed-sized pool using | |
| * {@code corePoolSize} threads and an unbounded queue, adjustments | |
| * to {@code maximumPoolSize} have no useful effect. Additionally, it | |
| * is almost never a good idea to set {@code corePoolSize} to zero or | |
| * use {@code allowCoreThreadTimeOut} because this may leave the pool | |
| * without threads to handle tasks once they become eligible to run. | |
| * | |
| * <p>As with {@code ThreadPoolExecutor}, if not otherwise specified, | |
| * this class uses {@link Executors#defaultThreadFactory} as the | |
| * default thread factory, and {@link ThreadPoolExecutor.AbortPolicy} | |
| * as the default rejected execution handler. | |
| * | |
| * <p><b>Extension notes:</b> This class overrides the | |
| * {@link ThreadPoolExecutor#execute(Runnable) execute} and | |
| * {@link AbstractExecutorService#submit(Runnable) submit} | |
| * methods to generate internal {@link ScheduledFuture} objects to | |
| * control per-task delays and scheduling. To preserve | |
| * functionality, any further overrides of these methods in | |
| * subclasses must invoke superclass versions, which effectively | |
| * disables additional task customization. However, this class | |
| * provides alternative protected extension method | |
| * {@code decorateTask} (one version each for {@code Runnable} and | |
| * {@code Callable}) that can be used to customize the concrete task | |
| * types used to execute commands entered via {@code execute}, | |
| * {@code submit}, {@code schedule}, {@code scheduleAtFixedRate}, | |
| * and {@code scheduleWithFixedDelay}. By default, a | |
| * {@code ScheduledThreadPoolExecutor} uses a task type extending | |
| * {@link FutureTask}. However, this may be modified or replaced using | |
| * subclasses of the form: | |
| * | |
| * <pre> {@code | |
| * public class CustomScheduledExecutor extends ScheduledThreadPoolExecutor { | |
| * | |
| * static class CustomTask<V> implements RunnableScheduledFuture<V> { ... } | |
| * | |
| * protected <V> RunnableScheduledFuture<V> decorateTask( | |
| * Runnable r, RunnableScheduledFuture<V> task) { | |
| * return new CustomTask<V>(r, task); | |
| * } | |
| * | |
| * protected <V> RunnableScheduledFuture<V> decorateTask( | |
| * Callable<V> c, RunnableScheduledFuture<V> task) { | |
| * return new CustomTask<V>(c, task); | |
| * } | |
| * // ... add constructors, etc. | |
| * }}</pre> | |
| * | |
| * @since 1.5 | |
| * @author Doug Lea | |
| */ | |
| public class ScheduledThreadPoolExecutor | |
| extends ThreadPoolExecutor | |
| implements ScheduledExecutorService { | |
| /* | |
| * This class specializes ThreadPoolExecutor implementation by | |
| * | |
| * 1. Using a custom task type ScheduledFutureTask, even for tasks | |
| * that don't require scheduling because they are submitted | |
| * using ExecutorService rather than ScheduledExecutorService | |
| * methods, which are treated as tasks with a delay of zero. | |
| * | |
| * 2. Using a custom queue (DelayedWorkQueue), a variant of | |
| * unbounded DelayQueue. The lack of capacity constraint and | |
| * the fact that corePoolSize and maximumPoolSize are | |
| * effectively identical simplifies some execution mechanics | |
| * (see delayedExecute) compared to ThreadPoolExecutor. | |
| * | |
| * 3. Supporting optional run-after-shutdown parameters, which | |
| * leads to overrides of shutdown methods to remove and cancel | |
| * tasks that should NOT be run after shutdown, as well as | |
| * different recheck logic when task (re)submission overlaps | |
| * with a shutdown. | |
| * | |
| * 4. Task decoration methods to allow interception and | |
| * instrumentation, which are needed because subclasses cannot | |
| * otherwise override submit methods to get this effect. These | |
| * don't have any impact on pool control logic though. | |
| */ | |
| /** | |
| * False if should cancel/suppress periodic tasks on shutdown. | |
| */ | |
| private volatile boolean continueExistingPeriodicTasksAfterShutdown; | |
| /** | |
| * False if should cancel non-periodic not-yet-expired tasks on shutdown. | |
| */ | |
| private volatile boolean executeExistingDelayedTasksAfterShutdown = true; | |
| /** | |
| * True if ScheduledFutureTask.cancel should remove from queue. | |
| */ | |
| volatile boolean removeOnCancel; | |
| /** | |
| * Sequence number to break scheduling ties, and in turn to | |
| * guarantee FIFO order among tied entries. | |
| */ | |
| private static final AtomicLong sequencer = new AtomicLong(); | |
| /** | |
| * Maximum delay is effectively 146 years | |
| */ | |
| private static final long MAX_NANOS = (Long.MAX_VALUE >>> 1) - 1; | |
| private class ScheduledFutureTask<V> | |
| extends FutureTask<V> implements RunnableScheduledFuture<V> { | |
| /** Sequence number to break ties FIFO */ | |
| private final long sequenceNumber; | |
| /** The nanoTime-based time when the task is enabled to execute. */ | |
| private volatile long time; | |
| /** | |
| * Period for repeating tasks, in nanoseconds. | |
| * A positive value indicates fixed-rate execution. | |
| * A negative value indicates fixed-delay execution. | |
| * A value of 0 indicates a non-repeating (one-shot) task. | |
| */ | |
| private final long period; | |
| /** The actual task to be re-enqueued by reExecutePeriodic */ | |
| RunnableScheduledFuture<V> outerTask = this; | |
| /** | |
| * Index into delay queue, to support faster cancellation. | |
| */ | |
| int heapIndex; | |
| /** | |
| * Creates a one-shot action with given nanoTime-based trigger time. | |
| */ | |
| ScheduledFutureTask(Runnable r, V result, long triggerTime, | |
| long sequenceNumber) { | |
| super(r, result); | |
| this.time = triggerTime; | |
| this.period = 0; | |
| this.sequenceNumber = sequenceNumber; | |
| } | |
| /** | |
| * Creates a periodic action with given nanoTime-based initial | |
| * trigger time and period. | |
| */ | |
| ScheduledFutureTask(Runnable r, V result, long triggerTime, | |
| long period, long sequenceNumber) { | |
| super(r, result); | |
| this.time = triggerTime; | |
| this.period = period; | |
| this.sequenceNumber = sequenceNumber; | |
| } | |
| /** | |
| * Creates a one-shot action with given nanoTime-based trigger time. | |
| */ | |
| ScheduledFutureTask(Callable<V> callable, long triggerTime, | |
| long sequenceNumber) { | |
| super(callable); | |
| this.time = triggerTime; | |
| this.period = 0; | |
| this.sequenceNumber = sequenceNumber; | |
| } | |
| public long getDelay(TimeUnit unit) { | |
| return unit.convert(time - System.nanoTime(), NANOSECONDS); | |
| } | |
| public int compareTo(Delayed other) { | |
| if (other == this) // compare zero if same object | |
| return 0; | |
| if (other instanceof ScheduledFutureTask) { | |
| ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other; | |
| long diff = time - x.time; | |
| if (diff < 0) | |
| return -1; | |
| else if (diff > 0) | |
| return 1; | |
| else if (sequenceNumber < x.sequenceNumber) | |
| return -1; | |
| else | |
| return 1; | |
| } | |
| long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); | |
| return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; | |
| } | |
| /** | |
| * Returns {@code true} if this is a periodic (not a one-shot) action. | |
| * | |
| * @return {@code true} if periodic | |
| */ | |
| public boolean isPeriodic() { | |
| return period != 0; | |
| } | |
| /** | |
| * Sets the next time to run for a periodic task. | |
| */ | |
| private void setNextRunTime() { | |
| long p = period; | |
| if (p > 0) | |
| time += p; | |
| else | |
| time = triggerTime(-p); | |
| } | |
| public boolean cancel(boolean mayInterruptIfRunning) { | |
| // The racy read of heapIndex below is benign: | |
| // if heapIndex < 0, then OOTA guarantees that we have surely | |
| // been removed; else we recheck under lock in remove() | |
| boolean cancelled = super.cancel(mayInterruptIfRunning); | |
| if (cancelled && removeOnCancel && heapIndex >= 0) | |
| remove(this); | |
| return cancelled; | |
| } | |
| /** | |
| * Overrides FutureTask version so as to reset/requeue if periodic. | |
| */ | |
| public void run() { | |
| if (!canRunInCurrentRunState(this)) | |
| cancel(false); | |
| else if (!isPeriodic()) | |
| super.run(); | |
| else if (super.runAndReset()) { | |
| setNextRunTime(); | |
| reExecutePeriodic(outerTask); | |
| } | |
| } | |
| } | |
| /** | |
| * Returns true if can run a task given current run state and | |
| * run-after-shutdown parameters. | |
| */ | |
| boolean canRunInCurrentRunState(RunnableScheduledFuture<?> task) { | |
| if (!isShutdown()) | |
| return true; | |
| if (isStopped()) | |
| return false; | |
| return task.isPeriodic() | |
| ? continueExistingPeriodicTasksAfterShutdown | |
| : (executeExistingDelayedTasksAfterShutdown | |
| || task.getDelay(NANOSECONDS) <= 0); | |
| } | |
| /** | |
| * Main execution method for delayed or periodic tasks. If pool | |
| * is shut down, rejects the task. Otherwise adds task to queue | |
| * and starts a thread, if necessary, to run it. (We cannot | |
| * prestart the thread to run the task because the task (probably) | |
| * shouldn't be run yet.) If the pool is shut down while the task | |
| * is being added, cancel and remove it if required by state and | |
| * run-after-shutdown parameters. | |
| * | |
| * @param task the task | |
| */ | |
| private void delayedExecute(RunnableScheduledFuture<?> task) { | |
| if (isShutdown()) | |
| reject(task); | |
| else { | |
| super.getQueue().add(task); | |
| if (!canRunInCurrentRunState(task) && remove(task)) | |
| task.cancel(false); | |
| else | |
| ensurePrestart(); | |
| } | |
| } | |
| /** | |
| * Requeues a periodic task unless current run state precludes it. | |
| * Same idea as delayedExecute except drops task rather than rejecting. | |
| * | |
| * @param task the task | |
| */ | |
| void reExecutePeriodic(RunnableScheduledFuture<?> task) { | |
| if (canRunInCurrentRunState(task)) { | |
| super.getQueue().add(task); | |
| if (canRunInCurrentRunState(task) || !remove(task)) { | |
| ensurePrestart(); | |
| return; | |
| } | |
| } | |
| task.cancel(false); | |
| } | |
| /** | |
| * Cancels and clears the queue of all tasks that should not be run | |
| * due to shutdown policy. Invoked within super.shutdown. | |
| */ | |
| @Override void onShutdown() { | |
| BlockingQueue<Runnable> q = super.getQueue(); | |
| boolean keepDelayed = | |
| getExecuteExistingDelayedTasksAfterShutdownPolicy(); | |
| boolean keepPeriodic = | |
| getContinueExistingPeriodicTasksAfterShutdownPolicy(); | |
| // Traverse snapshot to avoid iterator exceptions | |
| // TODO: implement and use efficient removeIf | |
| // super.getQueue().removeIf(...); | |
| for (Object e : q.toArray()) { | |
| if (e instanceof RunnableScheduledFuture) { | |
| RunnableScheduledFuture<?> t = (RunnableScheduledFuture<?>)e; | |
| if ((t.isPeriodic() | |
| ? !keepPeriodic | |
| : (!keepDelayed && t.getDelay(NANOSECONDS) > 0)) | |
| || t.isCancelled()) { // also remove if already cancelled | |
| if (q.remove(t)) | |
| t.cancel(false); | |
| } | |
| } | |
| } | |
| tryTerminate(); | |
| } | |
| /** | |
| * Modifies or replaces the task used to execute a runnable. | |
| * This method can be used to override the concrete | |
| * class used for managing internal tasks. | |
| * The default implementation simply returns the given task. | |
| * | |
| * @param runnable the submitted Runnable | |
| * @param task the task created to execute the runnable | |
| * @param <V> the type of the task's result | |
| * @return a task that can execute the runnable | |
| * @since 1.6 | |
| */ | |
| protected <V> RunnableScheduledFuture<V> decorateTask( | |
| Runnable runnable, RunnableScheduledFuture<V> task) { | |
| return task; | |
| } | |
| /** | |
| * Modifies or replaces the task used to execute a callable. | |
| * This method can be used to override the concrete | |
| * class used for managing internal tasks. | |
| * The default implementation simply returns the given task. | |
| * | |
| * @param callable the submitted Callable | |
| * @param task the task created to execute the callable | |
| * @param <V> the type of the task's result | |
| * @return a task that can execute the callable | |
| * @since 1.6 | |
| */ | |
| protected <V> RunnableScheduledFuture<V> decorateTask( | |
| Callable<V> callable, RunnableScheduledFuture<V> task) { | |
| return task; | |
| } | |
| /** | |
| * The default keep-alive time for pool threads. | |
| * | |
| * Normally, this value is unused because all pool threads will be | |
| * core threads, but if a user creates a pool with a corePoolSize | |
| * of zero (against our advice), we keep a thread alive as long as | |
| * there are queued tasks. If the keep alive time is zero (the | |
| * historic value), we end up hot-spinning in getTask, wasting a | |
| * CPU. But on the other hand, if we set the value too high, and | |
| * users create a one-shot pool which they don't cleanly shutdown, | |
| * the pool's non-daemon threads will prevent JVM termination. A | |
| * small but non-zero value (relative to a JVM's lifetime) seems | |
| * best. | |
| */ | |
| private static final long DEFAULT_KEEPALIVE_MILLIS = 10L; | |
| /** | |
| * Creates a new {@code ScheduledThreadPoolExecutor} with the | |
| * given core pool size. | |
| * | |
| * @param corePoolSize the number of threads to keep in the pool, even | |
| * if they are idle, unless {@code allowCoreThreadTimeOut} is set | |
| * @throws IllegalArgumentException if {@code corePoolSize < 0} | |
| */ | |
| public ScheduledThreadPoolExecutor(int corePoolSize) { | |
| super(corePoolSize, Integer.MAX_VALUE, | |
| DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, | |
| new DelayedWorkQueue()); | |
| } | |
| /** | |
| * Creates a new {@code ScheduledThreadPoolExecutor} with the | |
| * given initial parameters. | |
| * | |
| * @param corePoolSize the number of threads to keep in the pool, even | |
| * if they are idle, unless {@code allowCoreThreadTimeOut} is set | |
| * @param threadFactory the factory to use when the executor | |
| * creates a new thread | |
| * @throws IllegalArgumentException if {@code corePoolSize < 0} | |
| * @throws NullPointerException if {@code threadFactory} is null | |
| */ | |
| public ScheduledThreadPoolExecutor(int corePoolSize, | |
| ThreadFactory threadFactory) { | |
| super(corePoolSize, Integer.MAX_VALUE, | |
| DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, | |
| new DelayedWorkQueue(), threadFactory); | |
| } | |
| /** | |
| * Creates a new {@code ScheduledThreadPoolExecutor} with the | |
| * given initial parameters. | |
| * | |
| * @param corePoolSize the number of threads to keep in the pool, even | |
| * if they are idle, unless {@code allowCoreThreadTimeOut} is set | |
| * @param handler the handler to use when execution is blocked | |
| * because the thread bounds and queue capacities are reached | |
| * @throws IllegalArgumentException if {@code corePoolSize < 0} | |
| * @throws NullPointerException if {@code handler} is null | |
| */ | |
| public ScheduledThreadPoolExecutor(int corePoolSize, | |
| RejectedExecutionHandler handler) { | |
| super(corePoolSize, Integer.MAX_VALUE, | |
| DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, | |
| new DelayedWorkQueue(), handler); | |
| } | |
| /** | |
| * Creates a new {@code ScheduledThreadPoolExecutor} with the | |
| * given initial parameters. | |
| * | |
| * @param corePoolSize the number of threads to keep in the pool, even | |
| * if they are idle, unless {@code allowCoreThreadTimeOut} is set | |
| * @param threadFactory the factory to use when the executor | |
| * creates a new thread | |
| * @param handler the handler to use when execution is blocked | |
| * because the thread bounds and queue capacities are reached | |
| * @throws IllegalArgumentException if {@code corePoolSize < 0} | |
| * @throws NullPointerException if {@code threadFactory} or | |
| * {@code handler} is null | |
| */ | |
| public ScheduledThreadPoolExecutor(int corePoolSize, | |
| ThreadFactory threadFactory, | |
| RejectedExecutionHandler handler) { | |
| super(corePoolSize, Integer.MAX_VALUE, | |
| DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, | |
| new DelayedWorkQueue(), threadFactory, handler); | |
| } | |
| /** | |
| * Returns the nanoTime-based trigger time of a delayed action. | |
| */ | |
| private long triggerTime(long delay, TimeUnit unit) { | |
| return triggerTime(unit.toNanos((delay < 0) ? 0 : delay)); | |
| } | |
| /** | |
| * Returns the nanoTime-based trigger time of a delayed action. | |
| */ | |
| long triggerTime(long delay) { | |
| return System.nanoTime() + Math.min(delay, MAX_NANOS); | |
| } | |
| /** | |
| * @throws RejectedExecutionException {@inheritDoc} | |
| * @throws NullPointerException {@inheritDoc} | |
| */ | |
| public ScheduledFuture<?> schedule(Runnable command, | |
| long delay, | |
| TimeUnit unit) { | |
| if (command == null || unit == null) | |
| throw new NullPointerException(); | |
| RunnableScheduledFuture<Void> t = decorateTask(command, | |
| new ScheduledFutureTask<Void>(command, null, | |
| triggerTime(delay, unit), | |
| sequencer.getAndIncrement())); | |
| delayedExecute(t); | |
| return t; | |
| } | |
| /** | |
| * @throws RejectedExecutionException {@inheritDoc} | |
| * @throws NullPointerException {@inheritDoc} | |
| */ | |
| public <V> ScheduledFuture<V> schedule(Callable<V> callable, | |
| long delay, | |
| TimeUnit unit) { | |
| if (callable == null || unit == null) | |
| throw new NullPointerException(); | |
| RunnableScheduledFuture<V> t = decorateTask(callable, | |
| new ScheduledFutureTask<V>(callable, | |
| triggerTime(delay, unit), | |
| sequencer.getAndIncrement())); | |
| delayedExecute(t); | |
| return t; | |
| } | |
| /** | |
| * Submits a periodic action that becomes enabled first after the | |
| * given initial delay, and subsequently with the given period; | |
| * that is, executions will commence after | |
| * {@code initialDelay}, then {@code initialDelay + period}, then | |
| * {@code initialDelay + 2 * period}, and so on. | |
| * | |
| * <p>The sequence of task executions continues indefinitely until | |
| * one of the following exceptional completions occur: | |
| * <ul> | |
| * <li>The task is {@linkplain Future#cancel explicitly cancelled} | |
| * via the returned future. | |
| * <li>Method {@link #shutdown} is called and the {@linkplain | |
| * #getContinueExistingPeriodicTasksAfterShutdownPolicy policy on | |
| * whether to continue after shutdown} is not set true, or method | |
| * {@link #shutdownNow} is called; also resulting in task | |
| * cancellation. | |
| * <li>An execution of the task throws an exception. In this case | |
| * calling {@link Future#get() get} on the returned future will throw | |
| * {@link ExecutionException}, holding the exception as its cause. | |
| * </ul> | |
| * Subsequent executions are suppressed. Subsequent calls to | |
| * {@link Future#isDone isDone()} on the returned future will | |
| * return {@code true}. | |
| * | |
| * <p>If any execution of this task takes longer than its period, then | |
| * subsequent executions may start late, but will not concurrently | |
| * execute. | |
| * | |
| * @throws RejectedExecutionException {@inheritDoc} | |
| * @throws NullPointerException {@inheritDoc} | |
| * @throws IllegalArgumentException {@inheritDoc} | |
| */ | |
| public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, | |
| long initialDelay, | |
| long period, | |
| TimeUnit unit) { | |
| if (command == null || unit == null) | |
| throw new NullPointerException(); | |
| if (period <= 0L) | |
| throw new IllegalArgumentException(); | |
| ScheduledFutureTask<Void> sft = | |
| new ScheduledFutureTask<Void>(command, | |
| null, | |
| triggerTime(initialDelay, unit), | |
| unit.toNanos(period), | |
| sequencer.getAndIncrement()); | |
| RunnableScheduledFuture<Void> t = decorateTask(command, sft); | |
| sft.outerTask = t; | |
| delayedExecute(t); | |
| return t; | |
| } | |
| /** | |
| * Submits a periodic action that becomes enabled first after the | |
| * given initial delay, and subsequently with the given delay | |
| * between the termination of one execution and the commencement of | |
| * the next. | |
| * | |
| * <p>The sequence of task executions continues indefinitely until | |
| * one of the following exceptional completions occur: | |
| * <ul> | |
| * <li>The task is {@linkplain Future#cancel explicitly cancelled} | |
| * via the returned future. | |
| * <li>Method {@link #shutdown} is called and the {@linkplain | |
| * #getContinueExistingPeriodicTasksAfterShutdownPolicy policy on | |
| * whether to continue after shutdown} is not set true, or method | |
| * {@link #shutdownNow} is called; also resulting in task | |
| * cancellation. | |
| * <li>An execution of the task throws an exception. In this case | |
| * calling {@link Future#get() get} on the returned future will throw | |
| * {@link ExecutionException}, holding the exception as its cause. | |
| * </ul> | |
| * Subsequent executions are suppressed. Subsequent calls to | |
| * {@link Future#isDone isDone()} on the returned future will | |
| * return {@code true}. | |
| * | |
| * @throws RejectedExecutionException {@inheritDoc} | |
| * @throws NullPointerException {@inheritDoc} | |
| * @throws IllegalArgumentException {@inheritDoc} | |
| */ | |
| public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, | |
| long initialDelay, | |
| long delay, | |
| TimeUnit unit) { | |
| if (command == null || unit == null) | |
| throw new NullPointerException(); | |
| if (delay <= 0L) | |
| throw new IllegalArgumentException(); | |
| ScheduledFutureTask<Void> sft = | |
| new ScheduledFutureTask<Void>(command, | |
| null, | |
| triggerTime(initialDelay, unit), | |
| -unit.toNanos(delay), | |
| sequencer.getAndIncrement()); | |
| RunnableScheduledFuture<Void> t = decorateTask(command, sft); | |
| sft.outerTask = t; | |
| delayedExecute(t); | |
| return t; | |
| } | |
| /** | |
| * Executes {@code command} with zero required delay. | |
| * This has effect equivalent to | |
| * {@link #schedule(Runnable,long,TimeUnit) schedule(command, 0, anyUnit)}. | |
| * Note that inspections of the queue and of the list returned by | |
| * {@code shutdownNow} will access the zero-delayed | |
| * {@link ScheduledFuture}, not the {@code command} itself. | |
| * | |
| * <p>A consequence of the use of {@code ScheduledFuture} objects is | |
| * that {@link ThreadPoolExecutor#afterExecute afterExecute} is always | |
| * called with a null second {@code Throwable} argument, even if the | |
| * {@code command} terminated abruptly. Instead, the {@code Throwable} | |
| * thrown by such a task can be obtained via {@link Future#get}. | |
| * | |
| * @throws RejectedExecutionException at discretion of | |
| * {@code RejectedExecutionHandler}, if the task | |
| * cannot be accepted for execution because the | |
| * executor has been shut down | |
| * @throws NullPointerException {@inheritDoc} | |
| */ | |
| public void execute(Runnable command) { | |
| schedule(command, 0, NANOSECONDS); | |
| } | |
| // Override AbstractExecutorService methods | |
| /** | |
| * @throws RejectedExecutionException {@inheritDoc} | |
| * @throws NullPointerException {@inheritDoc} | |
| */ | |
| public Future<?> submit(Runnable task) { | |
| return schedule(task, 0, NANOSECONDS); | |
| } | |
| /** | |
| * @throws RejectedExecutionException {@inheritDoc} | |
| * @throws NullPointerException {@inheritDoc} | |
| */ | |
| public <T> Future<T> submit(Runnable task, T result) { | |
| return schedule(Executors.callable(task, result), 0, NANOSECONDS); | |
| } | |
| /** | |
| * @throws RejectedExecutionException {@inheritDoc} | |
| * @throws NullPointerException {@inheritDoc} | |
| */ | |
| public <T> Future<T> submit(Callable<T> task) { | |
| return schedule(task, 0, NANOSECONDS); | |
| } | |
| /** | |
| * Sets the policy on whether to continue executing existing | |
| * periodic tasks even when this executor has been {@code shutdown}. | |
| * In this case, executions will continue until {@code shutdownNow} | |
| * or the policy is set to {@code false} when already shutdown. | |
| * This value is by default {@code false}. | |
| * | |
| * @param value if {@code true}, continue after shutdown, else don't | |
| * @see #getContinueExistingPeriodicTasksAfterShutdownPolicy | |
| */ | |
| public void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) { | |
| continueExistingPeriodicTasksAfterShutdown = value; | |
| if (!value && isShutdown()) | |
| onShutdown(); | |
| } | |
| /** | |
| * Gets the policy on whether to continue executing existing | |
| * periodic tasks even when this executor has been {@code shutdown}. | |
| * In this case, executions will continue until {@code shutdownNow} | |
| * or the policy is set to {@code false} when already shutdown. | |
| * This value is by default {@code false}. | |
| * | |
| * @return {@code true} if will continue after shutdown | |
| * @see #setContinueExistingPeriodicTasksAfterShutdownPolicy | |
| */ | |
| public boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() { | |
| return continueExistingPeriodicTasksAfterShutdown; | |
| } | |
| /** | |
| * Sets the policy on whether to execute existing delayed | |
| * tasks even when this executor has been {@code shutdown}. | |
| * In this case, these tasks will only terminate upon | |
| * {@code shutdownNow}, or after setting the policy to | |
| * {@code false} when already shutdown. | |
| * This value is by default {@code true}. | |
| * | |
| * @param value if {@code true}, execute after shutdown, else don't | |
| * @see #getExecuteExistingDelayedTasksAfterShutdownPolicy | |
| */ | |
| public void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) { | |
| executeExistingDelayedTasksAfterShutdown = value; | |
| if (!value && isShutdown()) | |
| onShutdown(); | |
| } | |
| /** | |
| * Gets the policy on whether to execute existing delayed | |
| * tasks even when this executor has been {@code shutdown}. | |
| * In this case, these tasks will only terminate upon | |
| * {@code shutdownNow}, or after setting the policy to | |
| * {@code false} when already shutdown. | |
| * This value is by default {@code true}. | |
| * | |
| * @return {@code true} if will execute after shutdown | |
| * @see #setExecuteExistingDelayedTasksAfterShutdownPolicy | |
| */ | |
| public boolean getExecuteExistingDelayedTasksAfterShutdownPolicy() { | |
| return executeExistingDelayedTasksAfterShutdown; | |
| } | |
| /** | |
| * Sets the policy on whether cancelled tasks should be immediately | |
| * removed from the work queue at time of cancellation. This value is | |
| * by default {@code false}. | |
| * | |
| * @param value if {@code true}, remove on cancellation, else don't | |
| * @see #getRemoveOnCancelPolicy | |
| * @since 1.7 | |
| */ | |
| public void setRemoveOnCancelPolicy(boolean value) { | |
| removeOnCancel = value; | |
| } | |
| /** | |
| * Gets the policy on whether cancelled tasks should be immediately | |
| * removed from the work queue at time of cancellation. This value is | |
| * by default {@code false}. | |
| * | |
| * @return {@code true} if cancelled tasks are immediately removed | |
| * from the queue | |
| * @see #setRemoveOnCancelPolicy | |
| * @since 1.7 | |
| */ | |
| public boolean getRemoveOnCancelPolicy() { | |
| return removeOnCancel; | |
| } | |
| /** | |
| * Initiates an orderly shutdown in which previously submitted | |
| * tasks are executed, but no new tasks will be accepted. | |
| * Invocation has no additional effect if already shut down. | |
| * | |
| * <p>This method does not wait for previously submitted tasks to | |
| * complete execution. Use {@link #awaitTermination awaitTermination} | |
| * to do that. | |
| * | |
| * <p>If the {@code ExecuteExistingDelayedTasksAfterShutdownPolicy} | |
| * has been set {@code false}, existing delayed tasks whose delays | |
| * have not yet elapsed are cancelled. And unless the {@code | |
| * ContinueExistingPeriodicTasksAfterShutdownPolicy} has been set | |
| * {@code true}, future executions of existing periodic tasks will | |
| * be cancelled. | |
| */ | |
| public void shutdown() { | |
| super.shutdown(); | |
| } | |
| /** | |
| * Attempts to stop all actively executing tasks, halts the | |
| * processing of waiting tasks, and returns a list of the tasks | |
| * that were awaiting execution. These tasks are drained (removed) | |
| * from the task queue upon return from this method. | |
| * | |
| * <p>This method does not wait for actively executing tasks to | |
| * terminate. Use {@link #awaitTermination awaitTermination} to | |
| * do that. | |
| * | |
| * <p>There are no guarantees beyond best-effort attempts to stop | |
| * processing actively executing tasks. This implementation | |
| * interrupts tasks via {@link Thread#interrupt}; any task that | |
| * fails to respond to interrupts may never terminate. | |
| * | |
| * @return list of tasks that never commenced execution. | |
| * Each element of this list is a {@link ScheduledFuture}. | |
| * For tasks submitted via one of the {@code schedule} | |
| * methods, the element will be identical to the returned | |
| * {@code ScheduledFuture}. For tasks submitted using | |
| * {@link #execute execute}, the element will be a | |
| * zero-delay {@code ScheduledFuture}. | |
| */ | |
| public List<Runnable> shutdownNow() { | |
| return super.shutdownNow(); | |
| } | |
| /** | |
| * Returns the task queue used by this executor. Access to the | |
| * task queue is intended primarily for debugging and monitoring. | |
| * This queue may be in active use. Retrieving the task queue | |
| * does not prevent queued tasks from executing. | |
| * | |
| * <p>Each element of this queue is a {@link ScheduledFuture}. | |
| * For tasks submitted via one of the {@code schedule} methods, the | |
| * element will be identical to the returned {@code ScheduledFuture}. | |
| * For tasks submitted using {@link #execute execute}, the element | |
| * will be a zero-delay {@code ScheduledFuture}. | |
| * | |
| * <p>Iteration over this queue is <em>not</em> guaranteed to traverse | |
| * tasks in the order in which they will execute. | |
| * | |
| * @return the task queue | |
| */ | |
| public BlockingQueue<Runnable> getQueue() { | |
| return super.getQueue(); | |
| } | |
| /** | |
| * Specialized delay queue. To mesh with TPE declarations, this | |
| * class must be declared as a BlockingQueue<Runnable> even though | |
| * it can only hold RunnableScheduledFutures. | |
| */ | |
| static class DelayedWorkQueue extends AbstractQueue<Runnable> | |
| implements BlockingQueue<Runnable> { | |
| /* | |
| * A DelayedWorkQueue is based on a heap-based data structure | |
| * like those in DelayQueue and PriorityQueue, except that | |
| * every ScheduledFutureTask also records its index into the | |
| * heap array. This eliminates the need to find a task upon | |
| * cancellation, greatly speeding up removal (down from O(n) | |
| * to O(log n)), and reducing garbage retention that would | |
| * otherwise occur by waiting for the element to rise to top | |
| * before clearing. But because the queue may also hold | |
| * RunnableScheduledFutures that are not ScheduledFutureTasks, | |
| * we are not guaranteed to have such indices available, in | |
| * which case we fall back to linear search. (We expect that | |
| * most tasks will not be decorated, and that the faster cases | |
| * will be much more common.) | |
| * | |
| * All heap operations must record index changes -- mainly | |
| * within siftUp and siftDown. Upon removal, a task's | |
| * heapIndex is set to -1. Note that ScheduledFutureTasks can | |
| * appear at most once in the queue (this need not be true for | |
| * other kinds of tasks or work queues), so are uniquely | |
| * identified by heapIndex. | |
| */ | |
| private static final int INITIAL_CAPACITY = 16; | |
| private RunnableScheduledFuture<?>[] queue = | |
| new RunnableScheduledFuture<?>[INITIAL_CAPACITY]; | |
| private final ReentrantLock lock = new ReentrantLock(); | |
| private int size; | |
| /** | |
| * Thread designated to wait for the task at the head of the | |
| * queue. This variant of the Leader-Follower pattern | |
| * (http://www.cs.wustl.edu/~schmidt/POSA/POSA2/) serves to | |
| * minimize unnecessary timed waiting. When a thread becomes | |
| * the leader, it waits only for the next delay to elapse, but | |
| * other threads await indefinitely. The leader thread must | |
| * signal some other thread before returning from take() or | |
| * poll(...), unless some other thread becomes leader in the | |
| * interim. Whenever the head of the queue is replaced with a | |
| * task with an earlier expiration time, the leader field is | |
| * invalidated by being reset to null, and some waiting | |
| * thread, but not necessarily the current leader, is | |
| * signalled. So waiting threads must be prepared to acquire | |
| * and lose leadership while waiting. | |
| */ | |
| private Thread leader; | |
| /** | |
| * Condition signalled when a newer task becomes available at the | |
| * head of the queue or a new thread may need to become leader. | |
| */ | |
| private final Condition available = lock.newCondition(); | |
| /** | |
| * Sets f's heapIndex if it is a ScheduledFutureTask. | |
| */ | |
| private static void setIndex(RunnableScheduledFuture<?> f, int idx) { | |
| if (f instanceof ScheduledFutureTask) | |
| ((ScheduledFutureTask)f).heapIndex = idx; | |
| } | |
| /** | |
| * Sifts element added at bottom up to its heap-ordered spot. | |
| * Call only when holding lock. | |
| */ | |
| private void siftUp(int k, RunnableScheduledFuture<?> key) { | |
| while (k > 0) { | |
| int parent = (k - 1) >>> 1; | |
| RunnableScheduledFuture<?> e = queue[parent]; | |
| if (key.compareTo(e) >= 0) | |
| break; | |
| queue[k] = e; | |
| setIndex(e, k); | |
| k = parent; | |
| } | |
| queue[k] = key; | |
| setIndex(key, k); | |
| } | |
| /** | |
| * Sifts element added at top down to its heap-ordered spot. | |
| * Call only when holding lock. | |
| */ | |
| private void siftDown(int k, RunnableScheduledFuture<?> key) { | |
| int half = size >>> 1; | |
| while (k < half) { | |
| int child = (k << 1) + 1; | |
| RunnableScheduledFuture<?> c = queue[child]; | |
| int right = child + 1; | |
| if (right < size && c.compareTo(queue[right]) > 0) | |
| c = queue[child = right]; | |
| if (key.compareTo(c) <= 0) | |
| break; | |
| queue[k] = c; | |
| setIndex(c, k); | |
| k = child; | |
| } | |
| queue[k] = key; | |
| setIndex(key, k); | |
| } | |
| /** | |
| * Resizes the heap array. Call only when holding lock. | |
| */ | |
| private void grow() { | |
| int oldCapacity = queue.length; | |
| int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50% | |
| if (newCapacity < 0) // overflow | |
| newCapacity = Integer.MAX_VALUE; | |
| queue = Arrays.copyOf(queue, newCapacity); | |
| } | |
| /** | |
| * Finds index of given object, or -1 if absent. | |
| */ | |
| private int indexOf(Object x) { | |
| if (x != null) { | |
| if (x instanceof ScheduledFutureTask) { | |
| int i = ((ScheduledFutureTask) x).heapIndex; | |
| // Sanity check; x could conceivably be a | |
| // ScheduledFutureTask from some other pool. | |
| if (i >= 0 && i < size && queue[i] == x) | |
| return i; | |
| } else { | |
| for (int i = 0; i < size; i++) | |
| if (x.equals(queue[i])) | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| public boolean contains(Object x) { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| return indexOf(x) != -1; | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| public boolean remove(Object x) { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| int i = indexOf(x); | |
| if (i < 0) | |
| return false; | |
| setIndex(queue[i], -1); | |
| int s = --size; | |
| RunnableScheduledFuture<?> replacement = queue[s]; | |
| queue[s] = null; | |
| if (s != i) { | |
| siftDown(i, replacement); | |
| if (queue[i] == replacement) | |
| siftUp(i, replacement); | |
| } | |
| return true; | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| public int size() { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| return size; | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| public boolean isEmpty() { | |
| return size() == 0; | |
| } | |
| public int remainingCapacity() { | |
| return Integer.MAX_VALUE; | |
| } | |
| public RunnableScheduledFuture<?> peek() { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| return queue[0]; | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| public boolean offer(Runnable x) { | |
| if (x == null) | |
| throw new NullPointerException(); | |
| RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x; | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| int i = size; | |
| if (i >= queue.length) | |
| grow(); | |
| size = i + 1; | |
| if (i == 0) { | |
| queue[0] = e; | |
| setIndex(e, 0); | |
| } else { | |
| siftUp(i, e); | |
| } | |
| if (queue[0] == e) { | |
| leader = null; | |
| available.signal(); | |
| } | |
| } finally { | |
| lock.unlock(); | |
| } | |
| return true; | |
| } | |
| public void put(Runnable e) { | |
| offer(e); | |
| } | |
| public boolean add(Runnable e) { | |
| return offer(e); | |
| } | |
| public boolean offer(Runnable e, long timeout, TimeUnit unit) { | |
| return offer(e); | |
| } | |
| /** | |
| * Performs common bookkeeping for poll and take: Replaces | |
| * first element with last and sifts it down. Call only when | |
| * holding lock. | |
| * @param f the task to remove and return | |
| */ | |
| private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) { | |
| int s = --size; | |
| RunnableScheduledFuture<?> x = queue[s]; | |
| queue[s] = null; | |
| if (s != 0) | |
| siftDown(0, x); | |
| setIndex(f, -1); | |
| return f; | |
| } | |
| public RunnableScheduledFuture<?> poll() { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| RunnableScheduledFuture<?> first = queue[0]; | |
| return (first == null || first.getDelay(NANOSECONDS) > 0) | |
| ? null | |
| : finishPoll(first); | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| public RunnableScheduledFuture<?> take() throws InterruptedException { | |
| final ReentrantLock lock = this.lock; | |
| lock.lockInterruptibly(); | |
| try { | |
| for (;;) { | |
| RunnableScheduledFuture<?> first = queue[0]; | |
| if (first == null) | |
| available.await(); | |
| else { | |
| long delay = first.getDelay(NANOSECONDS); | |
| if (delay <= 0L) | |
| return finishPoll(first); | |
| first = null; // don't retain ref while waiting | |
| if (leader != null) | |
| available.await(); | |
| else { | |
| Thread thisThread = Thread.currentThread(); | |
| leader = thisThread; | |
| try { | |
| available.awaitNanos(delay); | |
| } finally { | |
| if (leader == thisThread) | |
| leader = null; | |
| } | |
| } | |
| } | |
| } | |
| } finally { | |
| if (leader == null && queue[0] != null) | |
| available.signal(); | |
| lock.unlock(); | |
| } | |
| } | |
| public RunnableScheduledFuture<?> poll(long timeout, TimeUnit unit) | |
| throws InterruptedException { | |
| long nanos = unit.toNanos(timeout); | |
| final ReentrantLock lock = this.lock; | |
| lock.lockInterruptibly(); | |
| try { | |
| for (;;) { | |
| RunnableScheduledFuture<?> first = queue[0]; | |
| if (first == null) { | |
| if (nanos <= 0L) | |
| return null; | |
| else | |
| nanos = available.awaitNanos(nanos); | |
| } else { | |
| long delay = first.getDelay(NANOSECONDS); | |
| if (delay <= 0L) | |
| return finishPoll(first); | |
| if (nanos <= 0L) | |
| return null; | |
| first = null; // don't retain ref while waiting | |
| if (nanos < delay || leader != null) | |
| nanos = available.awaitNanos(nanos); | |
| else { | |
| Thread thisThread = Thread.currentThread(); | |
| leader = thisThread; | |
| try { | |
| long timeLeft = available.awaitNanos(delay); | |
| nanos -= delay - timeLeft; | |
| } finally { | |
| if (leader == thisThread) | |
| leader = null; | |
| } | |
| } | |
| } | |
| } | |
| } finally { | |
| if (leader == null && queue[0] != null) | |
| available.signal(); | |
| lock.unlock(); | |
| } | |
| } | |
| public void clear() { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| for (int i = 0; i < size; i++) { | |
| RunnableScheduledFuture<?> t = queue[i]; | |
| if (t != null) { | |
| queue[i] = null; | |
| setIndex(t, -1); | |
| } | |
| } | |
| size = 0; | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| public int drainTo(Collection<? super Runnable> c) { | |
| return drainTo(c, Integer.MAX_VALUE); | |
| } | |
| public int drainTo(Collection<? super Runnable> c, int maxElements) { | |
| Objects.requireNonNull(c); | |
| if (c == this) | |
| throw new IllegalArgumentException(); | |
| if (maxElements <= 0) | |
| return 0; | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| int n = 0; | |
| for (RunnableScheduledFuture<?> first; | |
| n < maxElements | |
| && (first = queue[0]) != null | |
| && first.getDelay(NANOSECONDS) <= 0;) { | |
| c.add(first); // In this order, in case add() throws. | |
| finishPoll(first); | |
| ++n; | |
| } | |
| return n; | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| public Object[] toArray() { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| return Arrays.copyOf(queue, size, Object[].class); | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| public <T> T[] toArray(T[] a) { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| if (a.length < size) | |
| return (T[]) Arrays.copyOf(queue, size, a.getClass()); | |
| System.arraycopy(queue, 0, a, 0, size); | |
| if (a.length > size) | |
| a[size] = null; | |
| return a; | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| public Iterator<Runnable> iterator() { | |
| final ReentrantLock lock = this.lock; | |
| lock.lock(); | |
| try { | |
| return new Itr(Arrays.copyOf(queue, size)); | |
| } finally { | |
| lock.unlock(); | |
| } | |
| } | |
| /** | |
| * Snapshot iterator that works off copy of underlying q array. | |
| */ | |
| private class Itr implements Iterator<Runnable> { | |
| final RunnableScheduledFuture<?>[] array; | |
| int cursor; // index of next element to return; initially 0 | |
| int lastRet = -1; // index of last element returned; -1 if no such | |
| Itr(RunnableScheduledFuture<?>[] array) { | |
| this.array = array; | |
| } | |
| public boolean hasNext() { | |
| return cursor < array.length; | |
| } | |
| public Runnable next() { | |
| if (cursor >= array.length) | |
| throw new NoSuchElementException(); | |
| return array[lastRet = cursor++]; | |
| } | |
| public void remove() { | |
| if (lastRet < 0) | |
| throw new IllegalStateException(); | |
| DelayedWorkQueue.this.remove(array[lastRet]); | |
| lastRet = -1; | |
| } | |
| } | |
| } | |
| } |