blob: 358d3b69f1e68c5bb826db55c9108e5c936cf7ce [file]
/*
* 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 java.util.Arrays;
import jdk.internal.misc.Unsafe;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
/**
* An add-on for ForkJoinPools that provides scheduling for
* delayed and periodic tasks
*/
final class DelayScheduler extends Thread {
/*
* A DelayScheduler maintains a 4-ary heap (see
* https://en.wikipedia.org/wiki/D-ary_heap) based on trigger
* times (field ScheduledForkJoinTask.when) along with a pending
* queue of tasks submitted by other threads. When enabled (their
* delays elapse), tasks are relayed to the pool, or run directly
* if task.isImmediate. Immediate mode is designed for internal
* jdk usages in which the (non-blocking) action is to cancel,
* unblock or differently relay another async task (in some cases,
* this relies on user code to trigger async tasks). If
* processing encounters resource failures (possible when growing
* heap or ForkJoinPool WorkQueue arrays), tasks are cancelled.
*
* To reduce memory contention, the heap is maintained solely via
* local variables in method loop() (forcing noticeable code
* sprawl), recording only the current heap size when blocked to
* allow method canShutDown to conservatively check emptiness, and
* to report approximate current size for monitoring.
*
* The pending queue uses a design similar to ForkJoinTask.Aux
* queues: Incoming requests prepend (Treiber-stack-style) to the
* pending list. The scheduler thread takes and nulls out the
* entire list per step to process them as a batch. The pending
* queue may encounter contention and retries among requesters,
* but much less so versus the scheduler. It is possible to use
* multiple pending queues to reduce this form of contention but
* it doesn't seem worthwhile even under heavy loads.
*
* The implementation relies on the scheduler being a non-virtual
* final Thread subclass. Field "active" records whether the
* scheduler may have any pending tasks (and/or shutdown actions)
* to process, otherwise parking either indefinitely or until the
* next task deadline. Incoming pending tasks ensure active
* status, unparking if necessary. The scheduler thread sets
* status to inactive when there is apparently no work, and then
* rechecks before actually parking. The active field takes on a
* negative value on termination, as a sentinel used in pool
* termination checks as well as to suppress reactivation after
* terminating.
*
* We avoid the need for auxilliary data structures by embedding
* pending queue links, heap indices, and pool references inside
* ScheduledForkJoinTasks. (We use the same structure for both
* Runnable and Callable versions, since including an extra field
* in either case doesn't hurt -- it seems mildly preferable for
* these objects to be larger than other kinds of tasks to reduce
* false sharing during possibly frequent bookkeeping updates.) To
* reduce GC pressure and memory retention, these are nulled out
* as soon as possible.
*
* The implementation is designed to accommodate usages in which
* many or even most tasks are cancelled before executing (which
* is typical with IO-based timeouts). The use of a 4-ary heap (in
* which each element has up to 4 children) improves locality, and
* reduces the need for array movement and memory writes compared
* to a standard binary heap, at the expense of more expensive
* replace() operations (with about half the writes but twice the
* reads). Especially in the presence of cancellations, this is
* often faster because the replace method removes cancelled tasks
* seen while performing sift-down operations, in which case these
* elements are not further recorded or accessed, even before
* processing the removal request generated by method
* ScheduledForkJoinTask.cancel() (which is then a no-op or not
* generated at all).
*
* To ensure that comparisons do not encounter integer wraparound
* errors, times are offset with the most negative possible value
* (nanoTimeOffset) determined during static initialization.
* Negative delays are screened out before use.
*
* Upon noticing pool shutdown, delayed and/or periodic tasks are
* purged according to pool configuration and policy; the
* scheduler then tries to terminate the pool if the heap is
* empty. The asynchronicity of these steps with respect to pool
* runState weakens guarantees about exactly when purged tasks
* report isCancelled to callers (they do not run, but there may
* be a lag setting their status).
*/
ForkJoinPool pool; // read once and detached upon starting
volatile ScheduledForkJoinTask<?> pending; // submitted adds and removes
volatile int active; // 0: inactive, -1: stopped, +1: running
int restingSize; // written only before parking
volatile int cancelDelayedTasksOnShutdown; // policy control
int pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
int pad8, pad9, padA, padB, padC, padD, padE; // reduce false sharing
private static final int INITIAL_HEAP_CAPACITY = 1 << 6;
private static final int POOL_STOPPING = 1; // must match ForkJoinPool
static final long nanoTimeOffset = // Most negative possible time base
Math.min(System.nanoTime(), 0L) + Long.MIN_VALUE;
private static final Unsafe U; // for atomic operations
private static final long ACTIVE;
private static final long PENDING;
static {
U = Unsafe.getUnsafe();
Class<DelayScheduler> klass = DelayScheduler.class;
ACTIVE = U.objectFieldOffset(klass, "active");
PENDING = U.objectFieldOffset(klass, "pending");
}
DelayScheduler(ForkJoinPool p, String name) {
super(name);
setDaemon(true);
pool = p;
}
/**
* Returns System.nanoTime() with nanoTimeOffset
*/
static final long now() {
return nanoTimeOffset + System.nanoTime();
}
/**
* Ensures the scheduler is not parked unless stopped.
* Returns negative if already stopped
*/
final int signal() {
int state;
if ((state = active) == 0 && U.getAndBitwiseOrInt(this, ACTIVE, 1) == 0)
U.unpark(this);
return state;
}
/**
* Inserts the task (if nonnull) to pending queue, to add,
* remove, or ignore depending on task status when processed.
*/
final void pend(ScheduledForkJoinTask<?> task) {
ScheduledForkJoinTask<?> f = pending;
if (task != null) {
do {} while (
f != (f = (ScheduledForkJoinTask<?>)
U.compareAndExchangeReference(
this, PENDING, task.nextPending = f, task)));
signal();
}
}
/**
* Returns true if (momentarily) inactive and heap is empty
*/
final boolean canShutDown() {
return (active <= 0 && restingSize <= 0);
}
/**
* Returns the number of elements in heap when last idle
*/
final int lastStableSize() {
return (active < 0) ? 0 : restingSize;
}
/**
* Turns on cancelDelayedTasksOnShutdown policy
*/
final void cancelDelayedTasksOnShutdown() {
cancelDelayedTasksOnShutdown = 1;
signal();
}
/**
* Sets up and runs scheduling loop
*/
public final void run() {
ForkJoinPool p = pool;
pool = null; // detach
if (p == null) // failed initialization
active = -1;
else {
try {
loop(p);
} finally {
restingSize = 0;
active = -1;
p.tryStopIfShutdown(this);
}
}
}
/**
* After initialization, repeatedly:
* 1. If apparently no work,
* if active, set tentatively inactive,
* else park until next trigger time, or indefinitely if none
* 2. Process pending tasks in batches, to add or remove from heap
* 3. Check for shutdown, either exiting or preparing for shutdown when empty
* 4. Trigger all enabled tasks by submitting them to pool or run if immediate
*/
private void loop(ForkJoinPool p) {
if (p != null) { // currently always true
ScheduledForkJoinTask<?>[] h = // heap array
new ScheduledForkJoinTask<?>[INITIAL_HEAP_CAPACITY];
int cap = h.length, n = 0, prevRunStatus = 0; // n is heap size
long parkTime = 0L; // zero for untimed park
for (;;) { // loop until stopped
ScheduledForkJoinTask<?> q, t; int runStatus;
if ((q = pending) == null) {
restingSize = n;
if (active != 0) // deactivate and recheck
U.compareAndSetInt(this, ACTIVE, 1, 0);
else {
Thread.interrupted(); // clear before park
U.park(false, parkTime);
}
q = pending;
}
while (q != null && // process pending tasks
(t = (ScheduledForkJoinTask<?>)
U.getAndSetReference(this, PENDING, null)) != null) {
ScheduledForkJoinTask<?> next;
do {
int i;
if ((next = t.nextPending) != null)
t.nextPending = null;
if ((i = t.heapIndex) >= 0) {
t.heapIndex = -1; // remove cancelled task
if (i < cap && h[i] == t)
n = replace(h, i, n);
}
else if (n >= cap || n < 0)
t.trySetCancelled(); // couldn't resize
else {
long d = t.when; // add and sift up
if (t.status >= 0) {
ScheduledForkJoinTask<?> parent;
int k = n++, pk, newCap;
while (k > 0 &&
(parent = h[pk = (k - 1) >>> 2]) != null &&
(parent.when > d)) {
parent.heapIndex = k;
h[k] = parent;
k = pk;
}
t.heapIndex = k;
h[k] = t;
if (n >= cap && (newCap = cap << 1) > cap) {
ScheduledForkJoinTask<?>[] a = null;
try { // try to resize
a = Arrays.copyOf(h, newCap);
} catch (Error | RuntimeException ex) {
}
if (a != null && a.length == newCap) {
cap = newCap;
h = a; // else keep using old array
}
}
}
}
} while ((t = next) != null);
q = pending;
}
if ((runStatus = p.shutdownStatus(this)) != 0) {
if ((n = tryStop(p, h, n, runStatus, prevRunStatus)) < 0)
break;
prevRunStatus = runStatus;
}
parkTime = 0L;
if (n > 0 && h.length > 0) { // submit enabled tasks
long now = now();
do {
ScheduledForkJoinTask<?> f; int stat;
if ((f = h[0]) != null) {
long d = f.when - now;
if ((stat = f.status) >= 0 && d > 0L) {
parkTime = d;
break;
}
f.heapIndex = -1;
if (stat < 0)
; // already cancelled
else if (f.isImmediate)
f.doExec();
else {
try {
p.executeEnabledScheduledTask(f);
}
catch (Error | RuntimeException ex) {
f.trySetCancelled();
}
}
}
} while ((n = replace(h, 0, n)) > 0);
}
}
}
}
/**
* Replaces removed heap element at index k, along with other
* cancelled nodes found while doing so.
* @return current heap size
*/
private static int replace(ScheduledForkJoinTask<?>[] h, int k, int n) {
if (h != null && h.length >= n) { // hoist checks
while (k >= 0 && n > k) {
int alsoReplace = -1; // non-negative if cancelled task seen
ScheduledForkJoinTask<?> t = null, u;
long d = 0L;
while (--n > k) { // find uncancelled replacement
if ((u = h[n]) != null) {
h[n] = null;
d = u.when;
if (u.status >= 0) {
t = u;
break;
}
u.heapIndex = -1;
}
}
if (t != null) { // sift down
for (int cs; (cs = (k << 2) + 1) < n; ) {
ScheduledForkJoinTask<?> leastChild = null, c;
int leastIndex = 0;
long leastValue = Long.MAX_VALUE;
for (int ck = cs, j = 4;;) { // at most 4 children
if ((c = h[ck]) == null)
break;
long cd = c.when;
if (c.status < 0 && alsoReplace < 0) {
alsoReplace = ck; // at most once per pass
c.heapIndex = -1;
}
else if (leastChild == null || cd < leastValue) {
leastValue = cd;
leastIndex = ck;
leastChild = c;
}
if (--j == 0 || ++ck >= n)
break;
}
if (leastChild == null || d <= leastValue)
break;
leastChild.heapIndex = k;
h[k] = leastChild;
k = leastIndex;
}
t.heapIndex = k;
}
h[k] = t;
k = alsoReplace;
}
}
return n;
}
/**
* Call only when pool run status is nonzero. Possibly cancels
* tasks and stops during pool shutdown and termination. If called
* when shutdown but not stopping, removes tasks according to
* policy if not already done so, and if not empty or pool not
* terminating, returns. Otherwise, cancels all tasks in heap and
* pending queue.
* @return negative if stop, else current heap size.
*/
private int tryStop(ForkJoinPool p, ScheduledForkJoinTask<?>[] h, int n,
int runStatus, int prevRunStatus) {
if ((runStatus & POOL_STOPPING) == 0) {
if (n > 0) {
if (cancelDelayedTasksOnShutdown != 0) {
cancelAll(h, n);
n = 0;
}
else if (prevRunStatus == 0 && h != null && h.length >= n) {
ScheduledForkJoinTask<?> t; int stat; // remove periodic tasks
for (int i = n - 1; i >= 0; --i) {
if ((t = h[i]) != null &&
((stat = t.status) < 0 || t.nextDelay != 0L)) {
t.heapIndex = -1;
if (stat >= 0)
t.trySetCancelled();
n = replace(h, i, n);
}
}
}
}
if (n > 0 || p == null || !p.tryStopIfShutdown(this))
return n; // check for quiescent shutdown
}
if (n > 0)
cancelAll(h, n);
for (ScheduledForkJoinTask<?> a = (ScheduledForkJoinTask<?>)
U.getAndSetReference(this, PENDING, null);
a != null; a = a.nextPending)
a.trySetCancelled(); // clear pending requests
return -1;
}
private static void cancelAll(ScheduledForkJoinTask<?>[] h, int n) {
if (h != null && h.length >= n) {
ScheduledForkJoinTask<?> t;
for (int i = 0; i < n; ++i) {
if ((t = h[i]) != null) {
h[i] = null;
t.heapIndex = -1;
t.trySetCancelled();
}
}
}
}
/**
* Task class for DelayScheduler operations
*/
@SuppressWarnings("serial") // Not designed to be serializable
static final class ScheduledForkJoinTask<T>
extends ForkJoinTask.InterruptibleTask<T>
implements ScheduledFuture<T> {
ForkJoinPool pool; // nulled out after use
Runnable runnable; // at most one of runnable or callable nonnull
Callable<? extends T> callable;
T result;
ScheduledForkJoinTask<?> nextPending; // for DelayScheduler pending queue
long when; // nanoTime-based trigger time
final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate
int heapIndex; // if non-negative, index on heap
final boolean isImmediate; // run by scheduler vs submitted when ready
/**
* Creates a new ScheduledForkJoinTask
* @param delay initial delay, in nanoseconds
* @param nextDelay 0 for one-shot, negative for fixed delay,
* positive for fixed rate, in nanoseconds
* @param isImmediate if action is to be performed
* by scheduler versus submitting to a WorkQueue
* @param runnable action (null if implementing callable version)
* @param callable function (null if implementing runnable versions)
* @param pool the pool for resubmissions and cancellations
* (disabled if null)
*/
public ScheduledForkJoinTask(long delay, long nextDelay,
boolean isImmediate, Runnable runnable,
Callable<T> callable, ForkJoinPool pool) {
this.when = DelayScheduler.now() + Math.max(delay, 0L);
this.heapIndex = -1;
this.nextDelay = nextDelay;
this.isImmediate = isImmediate;
this.runnable = runnable;
this.callable = callable;
this.pool = pool;
}
public void schedule() { // relay to pool, to allow independent use
ForkJoinPool p;
if ((p = pool) != null) // else already run
p.scheduleDelayedTask(this);
}
// InterruptibleTask methods
public final T getRawResult() { return result; }
public final void setRawResult(T v) { result = v; }
final Object adaptee() { return (runnable != null) ? runnable : callable; }
final T compute() throws Exception {
Callable<? extends T> c; Runnable r;
T res = null;
if ((r = runnable) != null)
r.run();
else if ((c = callable) != null)
res = c.call();
return res;
}
final boolean postExec() { // possibly resubmit
long d; ForkJoinPool p; DelayScheduler ds;
if ((d = nextDelay) != 0L && // is periodic
status >= 0 && // not abnormally completed
(p = pool) != null && (ds = p.delayScheduler) != null) {
if (p.shutdownStatus(ds) == 0) {
heapIndex = -1;
if (d < 0L)
when = DelayScheduler.now() - d;
else
when += d;
ds.pend(this);
return false;
}
trySetCancelled(); // pool is shutdown
}
pool = null; // reduce memory retention
runnable = null;
callable = null;
return true;
}
public final boolean cancel(boolean mayInterruptIfRunning) {
int s; ForkJoinPool p; DelayScheduler ds;
if ((s = trySetCancelled()) < 0)
return ((s & (ABNORMAL | THROWN)) == ABNORMAL);
if ((p = pool) != null &&
!interruptIfRunning(mayInterruptIfRunning)) {
pool = null;
runnable = null;
callable = null;
if (heapIndex >= 0 && nextPending == null &&
(ds = p.delayScheduler) != null)
ds.pend(this); // for heap cleanup
}
return true;
}
// ScheduledFuture methods
public final long getDelay(TimeUnit unit) {
return unit.convert(when - DelayScheduler.now(), NANOSECONDS);
}
public int compareTo(Delayed other) { // never used internally
long diff = (other instanceof ScheduledForkJoinTask<?> t) ?
when - t.when : // avoid nanoTime calls and conversions
getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
}
}