From cb1aedf669d46600f0f1be6249db57f02b53e603 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Mon, 6 Jan 2025 07:41:05 -0500 Subject: [PATCH 01/40] In-progress snapshot --- .../java/util/concurrent/ForkJoinPool.java | 363 +++++++++++++++++- .../java/util/concurrent/ForkJoinTask.java | 68 +++- .../concurrent/tck/ForkJoinPool20Test.java | 219 +++++++++++ 3 files changed, 647 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 73fa3eea1bd3a..c25271a08a3d2 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -38,6 +38,7 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -45,11 +46,13 @@ import java.util.function.Predicate; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.JavaUtilConcurrentFJPAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.vm.SharedThreadContainer; +import static java.util.concurrent.ForkJoinTask.DelayedTask; /** * An {@link ExecutorService} for running {@link ForkJoinTask}s. @@ -171,7 +174,8 @@ * @since 1.7 * @author Doug Lea */ -public class ForkJoinPool extends AbstractExecutorService { +public class ForkJoinPool extends AbstractExecutorService + implements ScheduledExecutorService { /* * Implementation Overview @@ -1596,6 +1600,7 @@ final boolean isApparentlyUnblocked() { final UncaughtExceptionHandler ueh; // per-worker UEH final SharedThreadContainer container; final String workerNamePrefix; // null for common pool + volatile DelayScheduler delayer; // lazily constructed WorkQueue[] queues; // main registry volatile long runState; // versioned, lockable final long keepAlive; // milliseconds before dropping if idle @@ -2005,7 +2010,7 @@ private int deactivate(WorkQueue w, int phase) { (qs = queues) == null || (n = qs.length) <= 0) return IDLE; // terminating int prechecks = Math.min(ac, 2); // reactivation threshold - for (int k = Math.max(n << 2, SPIN_WAITS << 1);;) { + for (int k = Math.max(n + (n << 1), SPIN_WAITS << 1);;) { WorkQueue q; int cap; ForkJoinTask[] a; long c; if (w.phase == activePhase) return activePhase; @@ -2721,8 +2726,15 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { e |= TERMINATED; if ((getAndBitwiseOrRunState(TERMINATED) & TERMINATED) == 0L) { CountDownLatch done; SharedThreadContainer ctr; + DelayScheduler ds; Thread dt; if ((done = termination) != null) done.countDown(); + if ((ds = delayer) != null && (dt = ds.scheduler) != null) { + try { + dt.interrupt(); + } catch (Throwable ignore) { + } + } if ((ctr = container) != null) ctr.close(); } @@ -3349,6 +3361,353 @@ public T invokeAny(Collection> tasks, .invokeAny(tasks, this, true, unit.toNanos(timeout)); } + static final class DelayScheduler implements Runnable { + static final int INITIAL_HEAP_CAPACITY = 1 << 6; + final Thread scheduler; + final ConcurrentLinkedQueue> pending; + final ReentrantLock lock; + final ForkJoinPool pool; + DelayedTask[] heap; + int size; + + DelayScheduler(ForkJoinPool p) { + pool = p; + heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; + pending = new ConcurrentLinkedQueue>(); + lock = new ReentrantLock(); + scheduler = new Thread(this); + } + + final void schedule(DelayedTask task) { + ReentrantLock lock = this.lock; + ConcurrentLinkedQueue> ts = pending; + if (task != null && lock != null && ts != null) { + if (!lock.tryLock()) { + ts.offer(task); + if (!lock.tryLock()) + return; + task = null; + } + processHeap(task, false); + } + } + + public void run() { + ReentrantLock lock = this.lock; + ForkJoinPool p = pool; + if (lock != null && p != null) { + while ((p.runState & STOP) == 0L) { + boolean locked = false; long waitTime; + try { + lock.lockInterruptibly(); + locked = true; + } catch (InterruptedException ok) { + } + if (!locked) + ; // recheck termination + else if ((waitTime = + processHeap(null, true)) < 0L) + ; + else if (waitTime == 0L) + LockSupport.park(this); + else + LockSupport.parkNanos(this, waitTime); + } + lock.lock(); + try { + DelayedTask[] h; int cap; + if ((h = heap) != null && h.length >= size) { + while (size > 0) { + DelayedTask t; + int s = --size; + if ((t = h[s]) != null) { + h[s] = null; + try { + t.cancel(false); + } catch (Throwable ignore) { + } + } + } + } + } finally { + lock.unlock(); + } + } + } + + private long processHeap(DelayedTask task, boolean wait) { + long now = System.nanoTime(), waitTime = 0L; + boolean signal = false; + DelayedTask ready = null; + ReentrantLock lock = this.lock; + try { + DelayedTask[] h; int cap; + if ((h = heap) != null && (cap = h.length) >= size) { + for (int s = size;;) { // clear trailing cancelled tasks + DelayedTask t; + if (s < 1 || (t = h[s - 1]) == null || t.status >= 0) + break; + t.heapIndex = -1; + h[size = --s] = null; + } + DelayedTask first; // dequeue ready tasks + while (size > 0 && (first = h[0]) != null) { + DelayedTask replacement; + long d = first.when - now; + int stat = first.status, s; + if (stat < 0 || d <= 0L) { + first.heapIndex = -1; + h[0] = null; + if ((s = --size) > 0 && (replacement = h[s]) != null) { + h[s] = null; + siftDown(now, h, 0, replacement); + } + if (stat >= 0) { + first.nextReady = ready; + ready = first; + } + } + else { + waitTime = d; + break; + } + } + ConcurrentLinkedQueue> ts = pending; + while (task != null || // add or remove pending tasts + (ts != null && (task = ts.poll()) != null)) { + int s = size, idx = task.heapIndex, newCap; + if (task.status >= 0) { + if (task.when - now <= 0L) { + task.heapIndex = -1; + task.nextReady = ready; + ready = task; + } + else { + if (s >= cap && (newCap = cap << 1) > cap) { + try { + h = heap = Arrays.copyOf(heap, newCap); + cap = h.length; + } catch (Error | RuntimeException ex) { + } + } + if (s >= cap) + task.cancel(false); + else { + size = s + 1; + if (s == 0) + h[0] = task; + else + siftUp(now, h, s, task); + if (!wait && (s == 0 || h[0] == task)) + signal = true; + } + } + } + else if (s > 0 && idx >= 0 && idx < s && h[idx] == task) { + task.heapIndex = -1; + size = --s; + DelayedTask replacement = h[s]; + h[s] = null; + if (s != idx && replacement != null) { + siftDown(now, h, idx, replacement); + if (heap[idx] == replacement && + replacement.heapIndex == idx) + siftUp(now, h, idx, replacement); + } + } + task = null; + } + } + } finally { + if (lock != null) + lock.unlock(); + } + ForkJoinPool p; Thread st; + if (signal && (st = scheduler) != null) + LockSupport.unpark(st); + if (ready == null || (p = pool) == null) + return waitTime; + p.submitReadyDelayedTasks(ready); + return -1L; + } + + private void siftUp(long now, DelayedTask[] h, int k, + DelayedTask t) { + if (k >= 0 && h != null && k < h.length && t != null && t.status >= 0) { + long d = t.when - now; + int parent; DelayedTask p; + while (k > 0 && (p = h[parent = (k - 1) >>> 1]) != null && + (p.status < 0 || d < p.when - now)) { + p.heapIndex = k; + h[k] = p; + k = parent; + } + t.heapIndex = k; + h[k] = t; + } + } + + private void siftDown(long now, DelayedTask[] h, int k, + DelayedTask t) { + int n = size, half = n >>> 1; + if (k >= 0 && k < n && h != null && n < h.length && t != null) { + long d = (t.status < 0) ? Long.MAX_VALUE : t.when - now; + int child, right; DelayedTask c, r; + while (k < half && (c = h[child = (k << 1) + 1]) != null) { + long cd = (c.status < 0) ? Long.MAX_VALUE : c.when - now; + if ((right = child + 1) < n && (r = h[right]) != null) { + long rd = (r.status < 0) ? Long.MAX_VALUE : r.when - now; + if (rd < cd) { + cd = rd; + c = r; + child = right; + } + } + if (d <= cd) + break; + c.heapIndex = k; + h[k] = c; + k = child; + } + t.heapIndex = k; + h[k] = t; + } + } + } + + final void submitReadyDelayedTasks(DelayedTask task) { + while (task != null) { + long d; DelayScheduler ds; ConcurrentLinkedQueue> ts; + if ((runState & SHUTDOWN) == 0L) { + try { + poolSubmit(true, task); + } catch(Error | RuntimeException ex) { + task.cancel(false); + } + if ((d = task.nextDelay) > 0L && task.status >= 0 && + (ds = delayer) != null && (ts = ds.pending) != null) { + task.heapIndex = 0; + task.when = d + System.nanoTime(); + ts.offer(task); + LockSupport.unpark(ds.scheduler); + } + } + DelayedTask next = task.nextReady; + task.nextReady = null; + task = next; + } + } + + final DelayScheduler startDelayScheduler() { + DelayScheduler ds; Thread st = null; + lockRunState(); + try { + if ((ds = delayer) == null) + st = (ds = delayer = new DelayScheduler(this)).scheduler; + } finally { + unlockRunState(); + } + if (st != null) { // start outside of lock + SharedThreadContainer ctr = container; + st.setDaemon(true); + st.setName("DelayScheduler"); + if (ctr != null) + ctr.start(st); + else + st.start(); + } + return ds; + } + + /** Maximum supported delay value */ + private static final long MAX_NANOS = (Long.MAX_VALUE >>> 1) - 1; + + public ScheduledFuture schedule(Runnable command, + long delay, + TimeUnit unit) { + DelayScheduler ds; + if (command == null || unit == null) + throw new NullPointerException(); + long d = Math.min(unit.toNanos(delay), MAX_NANOS); + DelayedTask task = new DelayedTask(command, null, this, d, 0L); + if (d <= 0L) + poolSubmit(true, task); + else if ((runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + else { + if ((ds = delayer) == null) + ds = startDelayScheduler(); + ds.schedule(task); + } + return task; + } + + public ScheduledFuture schedule(Callable callable, + long delay, + TimeUnit unit) { + DelayScheduler ds; + if (callable == null || unit == null) + throw new NullPointerException(); + long d = Math.min(unit.toNanos(delay), MAX_NANOS); + DelayedTask task = new DelayedTask(null, callable, this, d, 0L); + if (d <= 0L) + poolSubmit(true, task); + else if ((runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + else { + if ((ds = delayer) == null) + ds = startDelayScheduler(); + ds.schedule(task); + } + return task; + } + + public ScheduledFuture scheduleAtFixedRate(Runnable command, + long initialDelay, + long period, + TimeUnit unit) { + DelayScheduler ds; + if (command == null || unit == null) + throw new NullPointerException(); + if (period <= 0L) + throw new IllegalArgumentException(); + long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); + long p = Math.min(unit.toNanos(period), MAX_NANOS); + DelayedTask task = new DelayedTask(command, null, this, d, -p); + if ((ds = delayer) == null) + ds = startDelayScheduler(); + if ((runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + else if (d <= 0) + submitReadyDelayedTasks(task); + else + ds.schedule(task); + return task; + } + + public ScheduledFuture scheduleWithFixedDelay(Runnable command, + long initialDelay, + long delay, + TimeUnit unit) { + DelayScheduler ds; + if (command == null || unit == null) + throw new NullPointerException(); + if (delay <= 0L) + throw new IllegalArgumentException(); + long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); + long p = Math.min(unit.toNanos(delay), MAX_NANOS); + DelayedTask task = new DelayedTask(command, null, this, d, p); + if ((ds = delayer) == null) + ds = startDelayScheduler(); + if ((runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + else if (d <= 0) + submitReadyDelayedTasks(task); + else + ds.schedule(task); + return task; + } + /** * Returns the factory used for constructing new workers. * diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 7c1e974aafa0b..b91e38866fd24 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -43,6 +43,7 @@ import java.util.RandomAccess; import java.util.concurrent.locks.LockSupport; import jdk.internal.misc.Unsafe; +import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * Abstract base class for tasks that run within a {@link ForkJoinPool}. @@ -1637,6 +1638,7 @@ abstract static class InterruptibleTask extends ForkJoinTask implements RunnableFuture { transient volatile Thread runner; abstract T compute() throws Exception; + abstract boolean postExec(); public final boolean exec() { Thread.interrupted(); Thread t = runner = Thread.currentThread(); @@ -1655,7 +1657,7 @@ public final boolean exec() { } finally { runner = null; } - return true; + return postExec(); } public boolean cancel(boolean mayInterruptIfRunning) { Thread t; @@ -1696,6 +1698,7 @@ static final class AdaptedInterruptibleCallable extends InterruptibleTask public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } final T compute() throws Exception { return callable.call(); } + final boolean postExec() { return true; } final Object adaptee() { return callable; } private static final long serialVersionUID = 2838392045355241008L; } @@ -1716,6 +1719,7 @@ static final class AdaptedInterruptibleRunnable extends InterruptibleTask public final T getRawResult() { return result; } public final void setRawResult(T v) { } final T compute() { runnable.run(); return result; } + final boolean postExec() { return true; } final Object adaptee() { return runnable; } private static final long serialVersionUID = 2838392045355241008L; } @@ -1733,6 +1737,7 @@ static final class RunnableExecuteAction extends InterruptibleTask { public final Void getRawResult() { return null; } public final void setRawResult(Void v) { } final Void compute() { runnable.run(); return null; } + final boolean postExec() { return true; } final Object adaptee() { return runnable; } void onAuxExceptionSet(Throwable ex) { // if a handler, invoke it Thread t; java.lang.Thread.UncaughtExceptionHandler h; @@ -1780,6 +1785,7 @@ else if (U.getAndAddInt(this, COUNT, -1) <= 1) { } } public final T compute() { return null; } // never forked + final boolean postExec() { return true; } public final T getRawResult() { return result; } public final void setRawResult(T v) { } @@ -1855,6 +1861,66 @@ final void onRootCompletion() { } public final Void getRawResult() { return null; } public final void setRawResult(Void v) { } + final boolean postExec() { return true; } final Object adaptee() { return callable; } } + + @SuppressWarnings("serial") + static final class DelayedTask extends InterruptibleTask + implements ScheduledFuture { + final Runnable runnable; // only one of runnable or callable nonnull + final Callable callable; + final ForkJoinPool pool; + T result; + DelayedTask nextReady; // for collecting ready tasks + final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate + long when; // nanoTime-based trigger time + int heapIndex; + + DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, + long delay, long nextDelay) { + this.runnable = runnable; + this.callable = callable; + this.pool = pool; + this.nextDelay = nextDelay; + when = delay + System.nanoTime(); + } + public final T getRawResult() { return result; } + public final void setRawResult(T v) { result = v; } + final boolean postExec() { // resubmit if fixedDelay + long d; ForkJoinPool p; ForkJoinPool.DelayScheduler ds; + if ((d = nextDelay) < 0L && status >= 0 && (p = pool) != null && + (ds = p.delayer) != null) { + heapIndex = 0; + when = -d + System.nanoTime(); + ds.schedule(this); + } + return d == 0L; + } + final T compute() throws Exception { + Callable c; Runnable r; + T res = null; + if ((r = runnable) != null) + r.run(); + else if ((c = callable) != null) + res = c.call(); + return res; + } + final Object adaptee() { return (runnable != null) ? runnable : callable; } + public final boolean cancel(boolean mayInterruptIfRunning) { + ForkJoinPool p; ForkJoinPool.DelayScheduler ds; DelayedTask s; + boolean stat = super.cancel(mayInterruptIfRunning); + if (heapIndex >= 0 && status < 0 && (p = pool) != null && + (ds = p.delayer) != null) + ds.schedule(this); // for heap cleanup + return stat; + } + public final long getDelay(TimeUnit unit) { + return unit.convert(when - System.nanoTime(), NANOSECONDS); + } + public int compareTo(Delayed other) { + long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); + return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; + } + } } diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index b35ab064d5337..f674bbd2b410b 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -31,13 +31,35 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.ForkJoinWorkerThread; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; +import java.util.stream.Stream; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import junit.framework.Test; import junit.framework.TestSuite; @@ -216,4 +238,201 @@ static ForkJoinWorkerThread submitBusyTask(ForkJoinPool pool) throws Exception { } return worker; } + + // additions for ScheduledExecutorService + + + /** + * delayed schedule of callable successfully executes after delay + */ + public void testSchedule1() throws Exception { + final ForkJoinPool p = new ForkJoinPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Callable task = new CheckedCallable<>() { + public Boolean realCall() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + return Boolean.TRUE; + }}; + Future f = p.schedule(task, timeoutMillis(), MILLISECONDS); + assertSame(Boolean.TRUE, f.get()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + assertEquals(0L, done.getCount()); + } + } + + /** + * delayed schedule of runnable successfully executes after delay + */ + public void testSchedule3() throws Exception { + final ForkJoinPool p = new ForkJoinPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + Future f = p.schedule(task, timeoutMillis(), MILLISECONDS); + await(done); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + + /** + * scheduleAtFixedRate executes runnable after given initial delay + */ + public void testSchedule4() throws Exception { + final ForkJoinPool p = new ForkJoinPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + ScheduledFuture f = + p.scheduleAtFixedRate(task, timeoutMillis(), + LONG_DELAY_MS, MILLISECONDS); + await(done); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + f.cancel(true); + } + } + + /** + * scheduleWithFixedDelay executes runnable after given initial delay + */ + public void testSchedule5() throws Exception { + final ForkJoinPool p = new ForkJoinPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + ScheduledFuture f = + p.scheduleWithFixedDelay(task, timeoutMillis(), + LONG_DELAY_MS, MILLISECONDS); + await(done); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + f.cancel(true); + } + } + + static class RunnableCounter implements Runnable { + AtomicInteger count = new AtomicInteger(0); + public void run() { count.getAndIncrement(); } + } + + /** + * scheduleAtFixedRate executes series of tasks at given rate. + * Eventually, it must hold that: + * cycles - 1 <= elapsedMillis/delay < cycles + */ + public void testFixedRateSequence() throws InterruptedException { + final ForkJoinPool p = new ForkJoinPool(4); + try (PoolCleaner cleaner = cleaner(p)) { + for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { + final long startTime = System.nanoTime(); + final int cycles = 8; + final CountDownLatch done = new CountDownLatch(cycles); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + final ScheduledFuture periodicTask = + p.scheduleAtFixedRate(task, 0, delay, MILLISECONDS); + final int totalDelayMillis = (cycles - 1) * delay; + await(done, totalDelayMillis + LONG_DELAY_MS); + periodicTask.cancel(true); + final long elapsedMillis = millisElapsedSince(startTime); + assertTrue(elapsedMillis >= totalDelayMillis); + if (elapsedMillis <= cycles * delay) + return; + // else retry with longer delay + } + fail("unexpected execution rate"); + } + } + + /** + * Submitting null tasks throws NullPointerException + */ + public void testNullTaskSubmission() { + final ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertNullTaskSubmissionThrowsNullPointerException(p); + } + } + + /** + * Submitted tasks are rejected when shutdown + */ + public void testSubmittedTasksRejectedWhenShutdown() throws InterruptedException { + final ForkJoinPool p = new ForkJoinPool(4); + final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + final CountDownLatch threadsStarted = new CountDownLatch(p.getParallelism()); + final CountDownLatch done = new CountDownLatch(1); + final Runnable r = () -> { + threadsStarted.countDown(); + for (;;) { + try { + done.await(); + return; + } catch (InterruptedException shutdownNowDeliberatelyIgnored) {} + }}; + final Callable c = () -> { + threadsStarted.countDown(); + for (;;) { + try { + done.await(); + return Boolean.TRUE; + } catch (InterruptedException shutdownNowDeliberatelyIgnored) {} + }}; + + try (PoolCleaner cleaner = cleaner(p, done)) { + for (int i = p.getParallelism(); i--> 0; ) { + switch (rnd.nextInt(4)) { + case 0: p.execute(r); break; + case 1: assertFalse(p.submit(r).isDone()); break; + case 2: assertFalse(p.submit(r, Boolean.TRUE).isDone()); break; + case 3: assertFalse(p.submit(c).isDone()); break; + } + } + + await(threadsStarted); + p.shutdownNow(); + done.countDown(); // release blocking tasks + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + + // assertTaskSubmissionsAreRejected(p); + } + } + /** + * A fixed delay task with overflowing period should not prevent a + * one-shot task from executing. + * https://bugs.openjdk.org/browse/JDK-8051859 + */ + @SuppressWarnings("FutureReturnValueIgnored") + public void testScheduleWithFixedDelay_overflow() throws Exception { + final CountDownLatch delayedDone = new CountDownLatch(1); + final CountDownLatch immediateDone = new CountDownLatch(1); + final ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + final Runnable delayed = () -> { + delayedDone.countDown(); + p.submit(() -> immediateDone.countDown()); + }; + p.scheduleWithFixedDelay(delayed, 0L, Long.MAX_VALUE, SECONDS); + await(delayedDone); + await(immediateDone); + } + } + } From 5c4ca21e3ded954a0fdc70cf64630db1512c118f Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Tue, 7 Jan 2025 11:18:13 -0500 Subject: [PATCH 02/40] Better conform to STPE --- .../java/util/concurrent/ForkJoinPool.java | 258 +++++++++--------- .../java/util/concurrent/ForkJoinTask.java | 32 ++- .../concurrent/tck/ForkJoinPool20Test.java | 63 +++++ 3 files changed, 211 insertions(+), 142 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index c25271a08a3d2..0c9fd8a31e525 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -2711,7 +2711,10 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { } if (now) { + DelayScheduler ds; releaseWaiters(); + if ((ds = delayer) != null && !ds.terminated) + ds.shutdown(); for (;;) { if (((e = runState) & CLEANED) == 0L) { boolean clean = cleanQueues(); @@ -2726,15 +2729,8 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { e |= TERMINATED; if ((getAndBitwiseOrRunState(TERMINATED) & TERMINATED) == 0L) { CountDownLatch done; SharedThreadContainer ctr; - DelayScheduler ds; Thread dt; if ((done = termination) != null) done.countDown(); - if ((ds = delayer) != null && (dt = ds.scheduler) != null) { - try { - dt.interrupt(); - } catch (Throwable ignore) { - } - } if ((ctr = container) != null) ctr.close(); } @@ -3365,37 +3361,23 @@ static final class DelayScheduler implements Runnable { static final int INITIAL_HEAP_CAPACITY = 1 << 6; final Thread scheduler; final ConcurrentLinkedQueue> pending; - final ReentrantLock lock; + final ReentrantLock heapLock; final ForkJoinPool pool; DelayedTask[] heap; - int size; + int heapSize; + volatile boolean terminated; DelayScheduler(ForkJoinPool p) { pool = p; heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; pending = new ConcurrentLinkedQueue>(); - lock = new ReentrantLock(); + heapLock = new ReentrantLock(); scheduler = new Thread(this); } - final void schedule(DelayedTask task) { - ReentrantLock lock = this.lock; - ConcurrentLinkedQueue> ts = pending; - if (task != null && lock != null && ts != null) { - if (!lock.tryLock()) { - ts.offer(task); - if (!lock.tryLock()) - return; - task = null; - } - processHeap(task, false); - } - } - - public void run() { - ReentrantLock lock = this.lock; - ForkJoinPool p = pool; - if (lock != null && p != null) { + public final void run() { + ReentrantLock lock; ForkJoinPool p; + if ((lock = this.heapLock) != null && (p = pool) != null) { while ((p.runState & STOP) == 0L) { boolean locked = false; long waitTime; try { @@ -3403,62 +3385,38 @@ public void run() { locked = true; } catch (InterruptedException ok) { } - if (!locked) - ; // recheck termination - else if ((waitTime = - processHeap(null, true)) < 0L) - ; - else if (waitTime == 0L) - LockSupport.park(this); - else - LockSupport.parkNanos(this, waitTime); - } - lock.lock(); - try { - DelayedTask[] h; int cap; - if ((h = heap) != null && h.length >= size) { - while (size > 0) { - DelayedTask t; - int s = --size; - if ((t = h[s]) != null) { - h[s] = null; - try { - t.cancel(false); - } catch (Throwable ignore) { - } - } - } - } - } finally { - lock.unlock(); + if (locked && (waitTime = processHeap(null, true)) >= 0L) + U.park(false, waitTime); } + shutdown(); } } - private long processHeap(DelayedTask task, boolean wait) { + final long processHeap(DelayedTask task, boolean wait) { long now = System.nanoTime(), waitTime = 0L; boolean signal = false; DelayedTask ready = null; - ReentrantLock lock = this.lock; + ReentrantLock lock = this.heapLock; try { DelayedTask[] h; int cap; - if ((h = heap) != null && (cap = h.length) >= size) { - for (int s = size;;) { // clear trailing cancelled tasks + if ((h = heap) != null && (cap = h.length) >= heapSize) { + for (int s = heapSize;;) { // clear trailing cancelled tasks DelayedTask t; if (s < 1 || (t = h[s - 1]) == null || t.status >= 0) break; t.heapIndex = -1; - h[size = --s] = null; + h[heapSize = --s] = null; } DelayedTask first; // dequeue ready tasks - while (size > 0 && (first = h[0]) != null) { + while (heapSize > 0 && (first = h[0]) != null) { DelayedTask replacement; long d = first.when - now; int stat = first.status, s; if (stat < 0 || d <= 0L) { first.heapIndex = -1; h[0] = null; - if ((s = --size) > 0 && (replacement = h[s]) != null) { + if ((s = --heapSize) > 0 && + (replacement = h[s]) != null) { h[s] = null; siftDown(now, h, 0, replacement); } @@ -3475,7 +3433,7 @@ private long processHeap(DelayedTask task, boolean wait) { ConcurrentLinkedQueue> ts = pending; while (task != null || // add or remove pending tasts (ts != null && (task = ts.poll()) != null)) { - int s = size, idx = task.heapIndex, newCap; + int s = heapSize, idx = task.heapIndex, newCap; if (task.status >= 0) { if (task.when - now <= 0L) { task.heapIndex = -1; @@ -3493,7 +3451,7 @@ private long processHeap(DelayedTask task, boolean wait) { if (s >= cap) task.cancel(false); else { - size = s + 1; + heapSize = s + 1; if (s == 0) h[0] = task; else @@ -3505,12 +3463,13 @@ private long processHeap(DelayedTask task, boolean wait) { } else if (s > 0 && idx >= 0 && idx < s && h[idx] == task) { task.heapIndex = -1; - size = --s; + heapSize = --s; DelayedTask replacement = h[s]; h[s] = null; if (s != idx && replacement != null) { siftDown(now, h, idx, replacement); if (heap[idx] == replacement && + idx < heapSize - 1 && replacement.heapIndex == idx) siftUp(now, h, idx, replacement); } @@ -3524,7 +3483,7 @@ else if (s > 0 && idx >= 0 && idx < s && h[idx] == task) { } ForkJoinPool p; Thread st; if (signal && (st = scheduler) != null) - LockSupport.unpark(st); + U.unpark(st); if (ready == null || (p = pool) == null) return waitTime; p.submitReadyDelayedTasks(ready); @@ -3549,7 +3508,7 @@ private void siftUp(long now, DelayedTask[] h, int k, private void siftDown(long now, DelayedTask[] h, int k, DelayedTask t) { - int n = size, half = n >>> 1; + int n = heapSize, half = n >>> 1; if (k >= 0 && k < n && h != null && n < h.length && t != null) { long d = (t.status < 0) ? Long.MAX_VALUE : t.when - now; int child, right; DelayedTask c, r; @@ -3573,23 +3532,101 @@ private void siftDown(long now, DelayedTask[] h, int k, h[k] = t; } } + + final void shutdown() { + ReentrantLock lock; DelayedTask[] h; + Thread st; ConcurrentLinkedQueue> ts; + if (!terminated && (lock = this.heapLock) != null) { + lock.lock(); + try { + terminated = true; + if ((h = heap) != null && h.length >= heapSize) { + while (heapSize > 0) { + DelayedTask t; int s; + if ((t = h[s = --heapSize]) != null) { + h[s] = null; + try { + t.cancel(false); + } catch (Throwable ignore) { + } + } + } + } + if ((ts = pending) != null) { + DelayedTask t; + while ((t = ts.poll()) != null) { + if (t.status >= 0 && t.heapIndex >= 0) { + try { + t.cancel(false); + } catch (Throwable ignore) { + } + } + } + } + } finally { + lock.unlock(); + } + if ((st = scheduler) != null && st != Thread.currentThread()) { + try { + st.interrupt(); + } catch (Throwable ignore) { + } + } + } + } + + } + + final void scheduleDelayedTask(DelayedTask task) { + DelayScheduler ds; ReentrantLock lock; + ConcurrentLinkedQueue> ts; + if ((ds = delayer) == null) { + Thread st = null; + lockRunState(); + try { + if ((ds = delayer) == null) + st = (ds = delayer = new DelayScheduler(this)).scheduler; + } finally { + unlockRunState(); + } + if (st != null) { // start outside of lock + SharedThreadContainer ctr = container; + st.setDaemon(true); + st.setName("DelayScheduler"); + if (ctr != null) + ctr.start(st); + else + st.start(); + } + } + if (ds != null && task != null && (lock = ds.heapLock) != null && + (ts = ds.pending) != null) { + if (!lock.tryLock()) { + ts.offer(task); + if (lock.isLocked() || !lock.tryLock()) + return; + task = null; + } + ds.processHeap(task, false); + } } final void submitReadyDelayedTasks(DelayedTask task) { while (task != null) { - long d; DelayScheduler ds; ConcurrentLinkedQueue> ts; - if ((runState & SHUTDOWN) == 0L) { + boolean cancel = false; + if ((runState & SHUTDOWN) != 0L) + cancel = true; + else { try { poolSubmit(true, task); } catch(Error | RuntimeException ex) { - task.cancel(false); + cancel = true; } - if ((d = task.nextDelay) > 0L && task.status >= 0 && - (ds = delayer) != null && (ts = ds.pending) != null) { - task.heapIndex = 0; - task.when = d + System.nanoTime(); - ts.offer(task); - LockSupport.unpark(ds.scheduler); + } + if (cancel) { + try { + task.cancel(false); + } catch(Throwable ignore) { } } DelayedTask next = task.nextReady; @@ -3598,67 +3635,38 @@ final void submitReadyDelayedTasks(DelayedTask task) { } } - final DelayScheduler startDelayScheduler() { - DelayScheduler ds; Thread st = null; - lockRunState(); - try { - if ((ds = delayer) == null) - st = (ds = delayer = new DelayScheduler(this)).scheduler; - } finally { - unlockRunState(); - } - if (st != null) { // start outside of lock - SharedThreadContainer ctr = container; - st.setDaemon(true); - st.setName("DelayScheduler"); - if (ctr != null) - ctr.start(st); - else - st.start(); - } - return ds; - } - /** Maximum supported delay value */ private static final long MAX_NANOS = (Long.MAX_VALUE >>> 1) - 1; public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - DelayScheduler ds; if (command == null || unit == null) throw new NullPointerException(); + if ((runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); long d = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, 0L); if (d <= 0L) poolSubmit(true, task); - else if ((runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - else { - if ((ds = delayer) == null) - ds = startDelayScheduler(); - ds.schedule(task); - } + else + scheduleDelayedTask(task); return task; } public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - DelayScheduler ds; if (callable == null || unit == null) throw new NullPointerException(); + if ((runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); long d = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(null, callable, this, d, 0L); if (d <= 0L) poolSubmit(true, task); - else if ((runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - else { - if ((ds = delayer) == null) - ds = startDelayScheduler(); - ds.schedule(task); - } + else + scheduleDelayedTask(task); return task; } @@ -3666,22 +3674,19 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - DelayScheduler ds; if (command == null || unit == null) throw new NullPointerException(); if (period <= 0L) throw new IllegalArgumentException(); + if ((runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); long p = Math.min(unit.toNanos(period), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, -p); - if ((ds = delayer) == null) - ds = startDelayScheduler(); - if ((runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - else if (d <= 0) + if (d <= 0) submitReadyDelayedTasks(task); else - ds.schedule(task); + scheduleDelayedTask(task); return task; } @@ -3689,22 +3694,19 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - DelayScheduler ds; if (command == null || unit == null) throw new NullPointerException(); if (delay <= 0L) throw new IllegalArgumentException(); + if ((runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); long p = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, p); - if ((ds = delayer) == null) - ds = startDelayScheduler(); - if ((runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - else if (d <= 0) + if (d <= 0) submitReadyDelayedTasks(task); else - ds.schedule(task); + scheduleDelayedTask(task); return task; } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index b91e38866fd24..f283fb19f8628 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1875,27 +1875,31 @@ static final class DelayedTask extends InterruptibleTask DelayedTask nextReady; // for collecting ready tasks final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate long when; // nanoTime-based trigger time - int heapIndex; + int heapIndex; // index if queued on heap DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, long delay, long nextDelay) { + heapIndex = -1; + when = System.nanoTime() + delay; this.runnable = runnable; this.callable = callable; this.pool = pool; this.nextDelay = nextDelay; - when = delay + System.nanoTime(); } public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } - final boolean postExec() { // resubmit if fixedDelay - long d; ForkJoinPool p; ForkJoinPool.DelayScheduler ds; - if ((d = nextDelay) < 0L && status >= 0 && (p = pool) != null && - (ds = p.delayer) != null) { - heapIndex = 0; - when = -d + System.nanoTime(); - ds.schedule(this); - } - return d == 0L; + final boolean postExec() { // resubmit if periodic + long d; ForkJoinPool p; + if ((d = nextDelay) == 0L || status < 0 || + (p = pool) == null || p.isShutdown()) + return true; + heapIndex = -1; + if (d < 0L) + when = System.nanoTime() - d; + else + when += d; + p.scheduleDelayedTask(this); + return false; } final T compute() throws Exception { Callable c; Runnable r; @@ -1908,11 +1912,11 @@ else if ((c = callable) != null) } final Object adaptee() { return (runnable != null) ? runnable : callable; } public final boolean cancel(boolean mayInterruptIfRunning) { - ForkJoinPool p; ForkJoinPool.DelayScheduler ds; DelayedTask s; + ForkJoinPool p; boolean stat = super.cancel(mayInterruptIfRunning); if (heapIndex >= 0 && status < 0 && (p = pool) != null && - (ds = p.delayer) != null) - ds.schedule(this); // for heap cleanup + !p.isShutdown()) + p.scheduleDelayedTask(this); // for heap cleanup return stat; } public final long getDelay(TimeUnit unit) { diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index f674bbd2b410b..646865417c1c0 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -360,6 +360,50 @@ public void testFixedRateSequence() throws InterruptedException { fail("unexpected execution rate"); } } + /** + * scheduleWithFixedDelay executes series of tasks with given period. + * Eventually, it must hold that each task starts at least delay and at + * most 2 * delay after the termination of the previous task. + */ + public void testFixedDelaySequence() throws InterruptedException { + final ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { + final long startTime = System.nanoTime(); + final AtomicLong previous = new AtomicLong(startTime); + final AtomicBoolean tryLongerDelay = new AtomicBoolean(false); + final int cycles = 8; + final CountDownLatch done = new CountDownLatch(cycles); + final int d = delay; + final Runnable task = new CheckedRunnable() { + public void realRun() { + long now = System.nanoTime(); + long elapsedMillis + = NANOSECONDS.toMillis(now - previous.get()); + if (done.getCount() == cycles) { // first execution + if (elapsedMillis >= d) + tryLongerDelay.set(true); + } else { + if (elapsedMillis >= 2 * d) + tryLongerDelay.set(true); + } + previous.set(now); + done.countDown(); + }}; + final ScheduledFuture periodicTask = + p.scheduleWithFixedDelay(task, 0, delay, MILLISECONDS); + final int totalDelayMillis = (cycles - 1) * delay; + await(done, totalDelayMillis + cycles * LONG_DELAY_MS); + periodicTask.cancel(true); + final long elapsedMillis = millisElapsedSince(startTime); + assertTrue(elapsedMillis >= totalDelayMillis); + if (!tryLongerDelay.get()) + return; + // else retry with longer delay + } + fail("unexpected execution rate"); + } + } /** * Submitting null tasks throws NullPointerException @@ -434,5 +478,24 @@ public void testScheduleWithFixedDelay_overflow() throws Exception { await(immediateDone); } } + /** + * shutdownNow cancels tasks that were not run + */ + public void testShutdownNow_delayedTasks() throws InterruptedException { + final ForkJoinPool p = new ForkJoinPool(1); + List> tasks = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Runnable r = new NoOpRunnable(); + tasks.add(p.schedule(r, 9, SECONDS)); + tasks.add(p.scheduleAtFixedRate(r, 9, 9, SECONDS)); + tasks.add(p.scheduleWithFixedDelay(r, 9, 9, SECONDS)); + } + p.shutdownNow(); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + for (ScheduledFuture task : tasks) { + assertTrue(task.isDone()); + } + assertTrue(p.isTerminated()); + } } From bb0e6a6e372cca5b94a0b0aba2c69d7b255a518c Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Thu, 9 Jan 2025 09:10:15 -0500 Subject: [PATCH 03/40] Refactorings --- .../java/util/concurrent/ForkJoinPool.java | 196 ++++++++++-------- .../java/util/concurrent/ForkJoinTask.java | 7 +- 2 files changed, 107 insertions(+), 96 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 0c9fd8a31e525..89fe1bed49c93 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -2713,7 +2713,7 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { if (now) { DelayScheduler ds; releaseWaiters(); - if ((ds = delayer) != null && !ds.terminated) + if ((ds = delayer) != null && !ds.stopped) ds.shutdown(); for (;;) { if (((e = runState) & CLEANED) == 0L) { @@ -3365,7 +3365,7 @@ static final class DelayScheduler implements Runnable { final ForkJoinPool pool; DelayedTask[] heap; int heapSize; - volatile boolean terminated; + volatile boolean stopped; DelayScheduler(ForkJoinPool p) { pool = p; @@ -3396,34 +3396,20 @@ final long processHeap(DelayedTask task, boolean wait) { long now = System.nanoTime(), waitTime = 0L; boolean signal = false; DelayedTask ready = null; - ReentrantLock lock = this.heapLock; try { DelayedTask[] h; int cap; - if ((h = heap) != null && (cap = h.length) >= heapSize) { - for (int s = heapSize;;) { // clear trailing cancelled tasks - DelayedTask t; - if (s < 1 || (t = h[s - 1]) == null || t.status >= 0) - break; - t.heapIndex = -1; - h[heapSize = --s] = null; - } + if ((h = heap) != null && (cap = h.length) > 0) { DelayedTask first; // dequeue ready tasks - while (heapSize > 0 && (first = h[0]) != null) { - DelayedTask replacement; - long d = first.when - now; - int stat = first.status, s; - if (stat < 0 || d <= 0L) { + for (int s = heapSize; s > 0 && (first = h[0]) != null;) { + long d = first.when - now; int stat; + if ((stat = first.status) < 0 || d <= 0L) { first.heapIndex = -1; h[0] = null; - if ((s = --heapSize) > 0 && - (replacement = h[s]) != null) { - h[s] = null; - siftDown(now, h, 0, replacement); - } if (stat >= 0) { first.nextReady = ready; ready = first; } + s = heapSize = ((s > 1) ? replaceAt(now, h, 0, s) : 0); } else { waitTime = d; @@ -3436,10 +3422,14 @@ final long processHeap(DelayedTask task, boolean wait) { int s = heapSize, idx = task.heapIndex, newCap; if (task.status >= 0) { if (task.when - now <= 0L) { - task.heapIndex = -1; task.nextReady = ready; ready = task; } + else if (s == 0) { + h[0] = task; + signal = !wait; + heapSize = 1; + } else { if (s >= cap && (newCap = cap << 1) > cap) { try { @@ -3448,37 +3438,30 @@ final long processHeap(DelayedTask task, boolean wait) { } catch (Error | RuntimeException ex) { } } - if (s >= cap) - task.cancel(false); + if (s >= cap) { // can't grow + try { + task.cancel(false); + } catch (Throwable ignore) { + } + } else { - heapSize = s + 1; - if (s == 0) - h[0] = task; - else - siftUp(now, h, s, task); - if (!wait && (s == 0 || h[0] == task)) + heapSize = add(now, h, s, task); + if (!wait && h[0] == task) signal = true; } } } else if (s > 0 && idx >= 0 && idx < s && h[idx] == task) { + h[idx] = null; task.heapIndex = -1; - heapSize = --s; - DelayedTask replacement = h[s]; - h[s] = null; - if (s != idx && replacement != null) { - siftDown(now, h, idx, replacement); - if (heap[idx] == replacement && - idx < heapSize - 1 && - replacement.heapIndex == idx) - siftUp(now, h, idx, replacement); - } + heapSize = (s > 1) ? replaceAt(now, h, idx, s) : 0; } task = null; } } } finally { - if (lock != null) + ReentrantLock lock; + if ((lock = this.heapLock)!= null) lock.unlock(); } ForkJoinPool p; Thread st; @@ -3490,11 +3473,15 @@ else if (s > 0 && idx >= 0 && idx < s && h[idx] == task) { return -1L; } - private void siftUp(long now, DelayedTask[] h, int k, - DelayedTask t) { - if (k >= 0 && h != null && k < h.length && t != null && t.status >= 0) { + static int add(long now, DelayedTask[] h, int s, DelayedTask t) { + if (h != null && s >= 0 && s < h.length && t != null) { + DelayedTask u, p; + while (s > 0 && (u = h[s - 1]) != null && u.status < 0) { + u.heapIndex = -1; // clear trailing cancelled tasks + h[--s] = null; + } + int k = s++, parent, ck; long d = t.when - now; - int parent; DelayedTask p; while (k > 0 && (p = h[parent = (k - 1) >>> 1]) != null && (p.status < 0 || d < p.when - now)) { p.heapIndex = k; @@ -3504,42 +3491,54 @@ private void siftUp(long now, DelayedTask[] h, int k, t.heapIndex = k; h[k] = t; } + return s; } - private void siftDown(long now, DelayedTask[] h, int k, - DelayedTask t) { - int n = heapSize, half = n >>> 1; - if (k >= 0 && k < n && h != null && n < h.length && t != null) { - long d = (t.status < 0) ? Long.MAX_VALUE : t.when - now; - int child, right; DelayedTask c, r; - while (k < half && (c = h[child = (k << 1) + 1]) != null) { - long cd = (c.status < 0) ? Long.MAX_VALUE : c.when - now; - if ((right = child + 1) < n && (r = h[right]) != null) { - long rd = (r.status < 0) ? Long.MAX_VALUE : r.when - now; - if (rd < cd) { + static int replaceAt(long now, DelayedTask[] h, int k, int s) { + if (k >= 0 && s >= 0 && h != null && s <= h.length) { + DelayedTask t = null; + while (s > k) { // find uncancelled replacement + DelayedTask u = h[--s]; + h[s] = null; + if (u != null) { + if (u.status >= 0) { + t = u; + break; + } + u.heapIndex = -1; + } + } + if (t != null) { + int child, right; DelayedTask c, r; + long d = t.when - now, rd; + while ((child = (k << 1) + 1) < s && (c = h[child]) != null) { + long cd = (c.status < 0) ? Long.MAX_VALUE : c.when - now; + if ((right = child + 1) < s && (r = h[right]) != null && + r.status >= 0 && (rd = r.when - now) < cd) { cd = rd; c = r; child = right; } + if (d <= cd) + break; + c.heapIndex = k; + h[k] = c; + k = child; } - if (d <= cd) - break; - c.heapIndex = k; - h[k] = c; - k = child; + t.heapIndex = k; + h[k] = t; } - t.heapIndex = k; - h[k] = t; } + return s; } final void shutdown() { ReentrantLock lock; DelayedTask[] h; Thread st; ConcurrentLinkedQueue> ts; - if (!terminated && (lock = this.heapLock) != null) { + if ((lock = this.heapLock) != null) { lock.lock(); try { - terminated = true; + stopped = true; if ((h = heap) != null && h.length >= heapSize) { while (heapSize > 0) { DelayedTask t; int s; @@ -3577,9 +3576,23 @@ final void shutdown() { } - final void scheduleDelayedTask(DelayedTask task) { + final void scheduleDelayedTask(DelayedTask task, long delay, + boolean resubmit) { DelayScheduler ds; ReentrantLock lock; ConcurrentLinkedQueue> ts; + if (!resubmit && delay <= 0L) { + poolSubmit(true, task); + return; + } + if ((runState & SHUTDOWN) != 0L) { + if (!resubmit) + throw new RejectedExecutionException(); + try { + task.cancel(false); + } catch(Throwable ignore) { + } + return; + } if ((ds = delayer) == null) { Thread st = null; lockRunState(); @@ -3599,10 +3612,11 @@ final void scheduleDelayedTask(DelayedTask task) { st.start(); } } - if (ds != null && task != null && (lock = ds.heapLock) != null && + if (ds != null && (lock = ds.heapLock) != null && (ts = ds.pending) != null) { if (!lock.tryLock()) { - ts.offer(task); + if (task != null) + ts.offer(task); if (lock.isLocked() || !lock.tryLock()) return; task = null; @@ -3635,6 +3649,24 @@ final void submitReadyDelayedTasks(DelayedTask task) { } } + final void removeCancelledDelayedTask(DelayedTask task) { + DelayScheduler ds; ReentrantLock lock; + ConcurrentLinkedQueue> ts; + if ((runState & STOP) == 0L && + (ds = delayer) != null && (lock = ds.heapLock) != null && + (ts = ds.pending) != null && task != null) { + if (!lock.tryLock()) { + if (task.heapIndex < 0) + return; + ts.offer(task); + if (lock.isLocked() || !lock.tryLock()) + return; + task = null; + } + ds.processHeap(task, false); + } + } + /** Maximum supported delay value */ private static final long MAX_NANOS = (Long.MAX_VALUE >>> 1) - 1; @@ -3643,14 +3675,9 @@ public ScheduledFuture schedule(Runnable command, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); - if ((runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); long d = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, 0L); - if (d <= 0L) - poolSubmit(true, task); - else - scheduleDelayedTask(task); + scheduleDelayedTask(task, d, false); return task; } @@ -3659,14 +3686,9 @@ public ScheduledFuture schedule(Callable callable, TimeUnit unit) { if (callable == null || unit == null) throw new NullPointerException(); - if ((runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); long d = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(null, callable, this, d, 0L); - if (d <= 0L) - poolSubmit(true, task); - else - scheduleDelayedTask(task); + scheduleDelayedTask(task, d, false); return task; } @@ -3678,15 +3700,10 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, throw new NullPointerException(); if (period <= 0L) throw new IllegalArgumentException(); - if ((runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); long p = Math.min(unit.toNanos(period), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, -p); - if (d <= 0) - submitReadyDelayedTasks(task); - else - scheduleDelayedTask(task); + scheduleDelayedTask(task, d, false); return task; } @@ -3698,15 +3715,10 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, throw new NullPointerException(); if (delay <= 0L) throw new IllegalArgumentException(); - if ((runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); long p = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, p); - if (d <= 0) - submitReadyDelayedTasks(task); - else - scheduleDelayedTask(task); + scheduleDelayedTask(task, d, false); return task; } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index f283fb19f8628..37889a4943493 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1898,7 +1898,7 @@ final boolean postExec() { // resubmit if periodic when = System.nanoTime() - d; else when += d; - p.scheduleDelayedTask(this); + p.scheduleDelayedTask(this, 0L, true); return false; } final T compute() throws Exception { @@ -1914,9 +1914,8 @@ else if ((c = callable) != null) public final boolean cancel(boolean mayInterruptIfRunning) { ForkJoinPool p; boolean stat = super.cancel(mayInterruptIfRunning); - if (heapIndex >= 0 && status < 0 && (p = pool) != null && - !p.isShutdown()) - p.scheduleDelayedTask(this); // for heap cleanup + if (heapIndex >= 0 && status < 0 && (p = pool) != null) + p.removeCancelledDelayedTask(this); // for heap cleanup return stat; } public final long getDelay(TimeUnit unit) { From f683d7f35d3144ac760007d72f3e4935852adc28 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Thu, 9 Jan 2025 14:30:16 -0500 Subject: [PATCH 04/40] Use pendingRemval queue --- .../java/util/concurrent/ForkJoinPool.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 89fe1bed49c93..14eb1d3dc2c0c 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3361,6 +3361,7 @@ static final class DelayScheduler implements Runnable { static final int INITIAL_HEAP_CAPACITY = 1 << 6; final Thread scheduler; final ConcurrentLinkedQueue> pending; + final ConcurrentLinkedQueue> pendingRemoval; final ReentrantLock heapLock; final ForkJoinPool pool; DelayedTask[] heap; @@ -3371,6 +3372,7 @@ static final class DelayScheduler implements Runnable { pool = p; heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; pending = new ConcurrentLinkedQueue>(); + pendingRemoval = new ConcurrentLinkedQueue>(); heapLock = new ReentrantLock(); scheduler = new Thread(this); } @@ -3417,7 +3419,9 @@ final long processHeap(DelayedTask task, boolean wait) { } } ConcurrentLinkedQueue> ts = pending; + ConcurrentLinkedQueue> rs = pendingRemoval; while (task != null || // add or remove pending tasts + (rs != null && (task = rs.poll()) != null) || (ts != null && (task = ts.poll()) != null)) { int s = heapSize, idx = task.heapIndex, newCap; if (task.status >= 0) { @@ -3534,7 +3538,7 @@ static int replaceAt(long now, DelayedTask[] h, int k, int s) { final void shutdown() { ReentrantLock lock; DelayedTask[] h; - Thread st; ConcurrentLinkedQueue> ts; + Thread st; ConcurrentLinkedQueue> ts, rs; if ((lock = this.heapLock) != null) { lock.lock(); try { @@ -3544,9 +3548,12 @@ final void shutdown() { DelayedTask t; int s; if ((t = h[s = --heapSize]) != null) { h[s] = null; - try { - t.cancel(false); - } catch (Throwable ignore) { + t.heapIndex = -1; + if (t.status >= 0) { + try { + t.cancel(false); + } catch (Throwable ignore) { + } } } } @@ -3562,6 +3569,9 @@ final void shutdown() { } } } + if ((rs = pendingRemoval) != null) { + while (rs.poll() != null) ; + } } finally { lock.unlock(); } @@ -3651,14 +3661,14 @@ final void submitReadyDelayedTasks(DelayedTask task) { final void removeCancelledDelayedTask(DelayedTask task) { DelayScheduler ds; ReentrantLock lock; - ConcurrentLinkedQueue> ts; + ConcurrentLinkedQueue> rs; if ((runState & STOP) == 0L && (ds = delayer) != null && (lock = ds.heapLock) != null && - (ts = ds.pending) != null && task != null) { + (rs = ds.pendingRemoval) != null && task != null) { if (!lock.tryLock()) { if (task.heapIndex < 0) return; - ts.offer(task); + rs.offer(task); if (lock.isLocked() || !lock.tryLock()) return; task = null; From 3801ba0a25928bc08f27ccd7cdf5a9178e06ac59 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Fri, 10 Jan 2025 09:08:11 -0500 Subject: [PATCH 05/40] Reduce unparks --- .../java/util/concurrent/ForkJoinPool.java | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 14eb1d3dc2c0c..bbfa88467df1b 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3366,7 +3366,7 @@ static final class DelayScheduler implements Runnable { final ForkJoinPool pool; DelayedTask[] heap; int heapSize; - volatile boolean stopped; + volatile boolean stopped, sleeping; DelayScheduler(ForkJoinPool p) { pool = p; @@ -3387,8 +3387,11 @@ public final void run() { locked = true; } catch (InterruptedException ok) { } - if (locked && (waitTime = processHeap(null, true)) >= 0L) + if (locked && (waitTime = processHeap(null, true)) >= 0L && + sleeping) { U.park(false, waitTime); + sleeping = false; + } } shutdown(); } @@ -3396,7 +3399,7 @@ public final void run() { final long processHeap(DelayedTask task, boolean wait) { long now = System.nanoTime(), waitTime = 0L; - boolean signal = false; + boolean wakeScheduler = false; DelayedTask ready = null; try { DelayedTask[] h; int cap; @@ -3431,7 +3434,7 @@ final long processHeap(DelayedTask task, boolean wait) { } else if (s == 0) { h[0] = task; - signal = !wait; + wakeScheduler = !wait; heapSize = 1; } else { @@ -3451,7 +3454,7 @@ else if (s == 0) { else { heapSize = add(now, h, s, task); if (!wait && h[0] == task) - signal = true; + wakeScheduler = true; } } } @@ -3463,13 +3466,21 @@ else if (s > 0 && idx >= 0 && idx < s && h[idx] == task) { task = null; } } + if (wakeScheduler) { + if (!sleeping) + wakeScheduler = false; + else + sleeping = false; + } + else if (wait && ready == null) + sleeping = true; } finally { ReentrantLock lock; if ((lock = this.heapLock)!= null) lock.unlock(); } ForkJoinPool p; Thread st; - if (signal && (st = scheduler) != null) + if (wakeScheduler && (st = scheduler) != null) U.unpark(st); if (ready == null || (p = pool) == null) return waitTime; @@ -3623,15 +3634,17 @@ final void scheduleDelayedTask(DelayedTask task, long delay, } } if (ds != null && (lock = ds.heapLock) != null && - (ts = ds.pending) != null) { - if (!lock.tryLock()) { - if (task != null) - ts.offer(task); - if (lock.isLocked() || !lock.tryLock()) - return; + (ts = ds.pending) != null && task != null) { + boolean process = false; + if (!lock.isLocked() && !lock.hasQueuedThreads() && lock.tryLock()) + process = true; + else { + ts.offer(task); task = null; + process = lock.tryLock(); } - ds.processHeap(task, false); + if (process) + ds.processHeap(task, false); } } @@ -3665,15 +3678,16 @@ final void removeCancelledDelayedTask(DelayedTask task) { if ((runState & STOP) == 0L && (ds = delayer) != null && (lock = ds.heapLock) != null && (rs = ds.pendingRemoval) != null && task != null) { - if (!lock.tryLock()) { - if (task.heapIndex < 0) - return; + boolean process = false; + if (!lock.isLocked() && !lock.hasQueuedThreads() && lock.tryLock()) + process = true; + else { rs.offer(task); - if (lock.isLocked() || !lock.tryLock()) - return; task = null; + process = lock.tryLock(); } - ds.processHeap(task, false); + if (process) + ds.processHeap(task, false); } } From d630b40476e133b2cc061d409907c38697db234a Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Thu, 16 Jan 2025 12:42:36 -0500 Subject: [PATCH 06/40] Better pending queues --- .../java/util/concurrent/ForkJoinPool.java | 434 ++++++++---------- .../java/util/concurrent/ForkJoinTask.java | 15 +- .../concurrent/tck/ForkJoinPool20Test.java | 42 ++ 3 files changed, 254 insertions(+), 237 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index bbfa88467df1b..8ccdbb1acb280 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -45,6 +45,7 @@ import java.util.Objects; import java.util.function.Predicate; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; import jdk.internal.access.JavaLangAccess; @@ -2519,7 +2520,7 @@ final ForkJoinTask nextTaskFor(WorkQueue w) { * @param r current ThreadLocalRandom.getProbe() value * @param isSubmit false if this is for a common pool fork */ - private WorkQueue submissionQueue(int r) { + private WorkQueue submissionQueue(int r, boolean rejectOnShutdown) { if (r == 0) { ThreadLocalRandom.localInit(); // initialize caller's probe r = ThreadLocalRandom.getProbe(); @@ -2534,20 +2535,21 @@ private WorkQueue submissionQueue(int r) { if (w == null) w = new WorkQueue(null, id, 0, false); w.phase = id; - long isShutdown = lockRunState() & SHUTDOWN; - if (isShutdown == 0L && queues == qs && qs[i] == null) { + boolean reject = ((lockRunState() & SHUTDOWN) != 0 && + rejectOnShutdown); + if (!reject && queues == qs && qs[i] == null) { q = qs[i] = w; // else retry w = null; } unlockRunState(); if (q != null) return q; - if (isShutdown != 0L) + if (reject) break; } else if (!q.tryLockPhase()) // move index r = ThreadLocalRandom.advanceProbe(r); - else if ((runState & SHUTDOWN) != 0L) { + else if (rejectOnShutdown && (runState & SHUTDOWN) != 0L) { q.unlockPhase(); // check while q lock held break; } @@ -2566,7 +2568,7 @@ private void poolSubmit(boolean signalIfEmpty, ForkJoinTask task) { } else { // find and lock queue internal = false; - q = submissionQueue(ThreadLocalRandom.getProbe()); + q = submissionQueue(ThreadLocalRandom.getProbe(), true); } q.push(task, signalIfEmpty ? this : null, internal); } @@ -2580,7 +2582,7 @@ final WorkQueue externalSubmissionQueue() { int r = ThreadLocalRandom.getProbe(); return (((qs = queues) != null && (n = qs.length) > 0 && (q = qs[r & EXTERNAL_ID_MASK & (n - 1)]) != null && r != 0 && - q.tryLockPhase()) ? q : submissionQueue(r)); + q.tryLockPhase()) ? q : submissionQueue(r, true)); } /** @@ -2713,8 +2715,8 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { if (now) { DelayScheduler ds; releaseWaiters(); - if ((ds = delayer) != null && !ds.stopped) - ds.shutdown(); + if ((ds = delayer) != null) + ds.onTerminate(); for (;;) { if (((e = runState) & CLEANED) == 0L) { boolean clean = cleanQueues(); @@ -2725,6 +2727,8 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { break; if (ctl != 0L) // else loop if didn't finish cleaning break; + if ((ds = delayer) != null && !ds.stopped) + break; if ((e & CLEANED) != 0L) { e |= TERMINATED; if ((getAndBitwiseOrRunState(TERMINATED) & TERMINATED) == 0L) { @@ -3359,136 +3363,175 @@ public T invokeAny(Collection> tasks, static final class DelayScheduler implements Runnable { static final int INITIAL_HEAP_CAPACITY = 1 << 6; - final Thread scheduler; - final ConcurrentLinkedQueue> pending; - final ConcurrentLinkedQueue> pendingRemoval; - final ReentrantLock heapLock; final ForkJoinPool pool; + final Thread scheduler; + final AtomicReference> additions; + final AtomicReference> removals; DelayedTask[] heap; int heapSize; - volatile boolean stopped, sleeping; + volatile int active; + volatile boolean stopped; + private static final Unsafe U; + static final long ACTIVE; + static { + U = Unsafe.getUnsafe(); + Class klass = DelayScheduler.class; + ACTIVE = U.objectFieldOffset(klass, "active"); + } DelayScheduler(ForkJoinPool p) { pool = p; heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; - pending = new ConcurrentLinkedQueue>(); - pendingRemoval = new ConcurrentLinkedQueue>(); - heapLock = new ReentrantLock(); + removals = new AtomicReference>(); + additions = new AtomicReference>(); scheduler = new Thread(this); } + private boolean compareAndSetActive(int c, int v) { + return U.compareAndSetInt(this, ACTIVE, c, v); + } + + private int getAndBitwiseOrActive(int v) { + return U.getAndBitwiseOrInt(this, ACTIVE, v); + } + + private void activate() { + Thread st; + if ((st = scheduler) != null && active == 0 && + getAndBitwiseOrActive(1) == 0) + U.unpark(st); + } + + private void pend(DelayedTask task, AtomicReference> ts) { + if (ts != null && task != null) { + do {} while (!ts.compareAndSet(task.nextPending = ts.get(), task)); + activate(); + } + } + + private DelayedTask unpend(AtomicReference> ts) { + if (ts != null) { + for (DelayedTask t = ts.get(); t != null; ) { + if (t == (t = ts.compareAndExchange(t, null))) + return t; + } + } + return null; + } + + final void add(DelayedTask task) { + pend(task, additions); + } + + final void remove(DelayedTask task) { + pend(task, removals); + } + + final void onTerminate() { + if (!stopped) + activate(); + } + public final void run() { - ReentrantLock lock; ForkJoinPool p; - if ((lock = this.heapLock) != null && (p = pool) != null) { - while ((p.runState & STOP) == 0L) { - boolean locked = false; long waitTime; - try { - lock.lockInterruptibly(); - locked = true; - } catch (InterruptedException ok) { - } - if (locked && (waitTime = processHeap(null, true)) >= 0L && - sleeping) { - U.park(false, waitTime); - sleeping = false; - } + try { + while (!poolIsStopping(pool)) { + int wasActive = active; + submitReadyTasks(); + processPending(); + if (active == 1) + compareAndSetActive(1, 0); + else if (wasActive == 0) + deactivate(); } + } finally { + stopped = true; shutdown(); } } - final long processHeap(DelayedTask task, boolean wait) { - long now = System.nanoTime(), waitTime = 0L; - boolean wakeScheduler = false; - DelayedTask ready = null; - try { - DelayedTask[] h; int cap; - if ((h = heap) != null && (cap = h.length) > 0) { - DelayedTask first; // dequeue ready tasks - for (int s = heapSize; s > 0 && (first = h[0]) != null;) { - long d = first.when - now; int stat; - if ((stat = first.status) < 0 || d <= 0L) { - first.heapIndex = -1; - h[0] = null; - if (stat >= 0) { - first.nextReady = ready; - ready = first; - } - s = heapSize = ((s > 1) ? replaceAt(now, h, 0, s) : 0); - } + private void deactivate() { + DelayedTask[] h; DelayedTask t; + boolean wait = true; + long waitTime = 0L; + Thread.interrupted(); // clear before parking + if (heapSize > 0 && (h = heap) != null && + h.length > 0 && (t = h[0]) != null && + (waitTime = t.when - System.nanoTime()) <= 0L) + wait = false; + if (active == 0) { + if (wait) + U.park(false, waitTime); + active = 1; + } + } + + private void submitReadyTasks() { + ForkJoinPool p; DelayedTask[] h; int s; + if ((s = heapSize) > 0 && (p = pool) != null && + (h = heap) != null && h.length > 0) { + DelayedTask first; + long now = System.nanoTime(); + while ((first = h[0]) != null) { + long d = first.when - now; int stat; + if ((stat = first.status) >= 0 && d > 0L) + break; + first.heapIndex = -1; + h[0] = null; + s = heapSize = ((s > 1) ? heapReplace(now, h, 0, s) : 0); + if (stat >= 0) + p.submitReadyDelayedTask(first); + if (s == 0) + break; + } + } + } + + private void processPending() { + for (long now = System.nanoTime();;) { + DelayedTask t; DelayedTask[] h; ForkJoinPool p; + if ((t = unpend(removals)) == null && + (t = unpend(additions)) == null) + break; + while ((p = pool) != null && (h = heap) != null) { + int s = heapSize, cap = h.length, idx, newCap; + DelayedTask next; + if ((next = t.nextPending) != null) + t.nextPending = null; + if ((idx = t.heapIndex) < 0) { + if (t.status < 0) + ; + else if (t.when - now <= 0L) + p.submitReadyDelayedTask(t); else { - waitTime = d; - break; - } - } - ConcurrentLinkedQueue> ts = pending; - ConcurrentLinkedQueue> rs = pendingRemoval; - while (task != null || // add or remove pending tasts - (rs != null && (task = rs.poll()) != null) || - (ts != null && (task = ts.poll()) != null)) { - int s = heapSize, idx = task.heapIndex, newCap; - if (task.status >= 0) { - if (task.when - now <= 0L) { - task.nextReady = ready; - ready = task; - } - else if (s == 0) { - h[0] = task; - wakeScheduler = !wait; - heapSize = 1; + if (s >= cap && (newCap = cap << 1) > cap) { + try { + h = heap = Arrays.copyOf(heap, newCap); + cap = h.length; + } catch (Error | RuntimeException ex) { + } } + if (s >= cap) // can't grow + t.trySetCancelled(); + else if (s > 0) + heapSize = heapAdd(now, h, s, t); else { - if (s >= cap && (newCap = cap << 1) > cap) { - try { - h = heap = Arrays.copyOf(heap, newCap); - cap = h.length; - } catch (Error | RuntimeException ex) { - } - } - if (s >= cap) { // can't grow - try { - task.cancel(false); - } catch (Throwable ignore) { - } - } - else { - heapSize = add(now, h, s, task); - if (!wait && h[0] == task) - wakeScheduler = true; - } + h[0] = t; + heapSize = 1; } } - else if (s > 0 && idx >= 0 && idx < s && h[idx] == task) { - h[idx] = null; - task.heapIndex = -1; - heapSize = (s > 1) ? replaceAt(now, h, idx, s) : 0; - } - task = null; } + else if (idx < s && s <= cap && h[idx] == t) { + h[idx] = null; + t.heapIndex = -1; + heapSize = (s > 1) ? heapReplace(now, h, idx, s) : 0; + } + if ((t = next) == null) + break; } - if (wakeScheduler) { - if (!sleeping) - wakeScheduler = false; - else - sleeping = false; - } - else if (wait && ready == null) - sleeping = true; - } finally { - ReentrantLock lock; - if ((lock = this.heapLock)!= null) - lock.unlock(); } - ForkJoinPool p; Thread st; - if (wakeScheduler && (st = scheduler) != null) - U.unpark(st); - if (ready == null || (p = pool) == null) - return waitTime; - p.submitReadyDelayedTasks(ready); - return -1L; } - static int add(long now, DelayedTask[] h, int s, DelayedTask t) { + static int heapAdd(long now, DelayedTask[] h, int s, DelayedTask t) { if (h != null && s >= 0 && s < h.length && t != null) { DelayedTask u, p; while (s > 0 && (u = h[s - 1]) != null && u.status < 0) { @@ -3509,7 +3552,7 @@ static int add(long now, DelayedTask[] h, int s, DelayedTask t) { return s; } - static int replaceAt(long now, DelayedTask[] h, int k, int s) { + static int heapReplace(long now, DelayedTask[] h, int k, int s) { if (k >= 0 && s >= 0 && h != null && s <= h.length) { DelayedTask t = null; while (s > k) { // find uncancelled replacement @@ -3547,73 +3590,32 @@ static int replaceAt(long now, DelayedTask[] h, int k, int s) { return s; } - final void shutdown() { - ReentrantLock lock; DelayedTask[] h; - Thread st; ConcurrentLinkedQueue> ts, rs; - if ((lock = this.heapLock) != null) { - lock.lock(); - try { - stopped = true; - if ((h = heap) != null && h.length >= heapSize) { - while (heapSize > 0) { - DelayedTask t; int s; - if ((t = h[s = --heapSize]) != null) { - h[s] = null; - t.heapIndex = -1; - if (t.status >= 0) { - try { - t.cancel(false); - } catch (Throwable ignore) { - } - } - } - } - } - if ((ts = pending) != null) { - DelayedTask t; - while ((t = ts.poll()) != null) { - if (t.status >= 0 && t.heapIndex >= 0) { - try { - t.cancel(false); - } catch (Throwable ignore) { - } - } - } - } - if ((rs = pendingRemoval) != null) { - while (rs.poll() != null) ; - } - } finally { - lock.unlock(); - } - if ((st = scheduler) != null && st != Thread.currentThread()) { - try { - st.interrupt(); - } catch (Throwable ignore) { + private void shutdown() { + DelayedTask[] h; ForkJoinPool p; + if ((h = heap) != null && h.length >= heapSize) { + while (heapSize > 0) { + DelayedTask u; int s; + if ((u = h[s = --heapSize]) != null) { + h[s] = null; + u.heapIndex = -1; + u.trySetCancelled(); } } } - } + for (DelayedTask t; (t = unpend(additions)) != null; ) { + do { + t.trySetCancelled(); + } while ((t = t.nextPending) != null); + } + while (unpend(removals) != null) ; + if ((p = pool) != null) + p.tryTerminate(false, false); + } } - final void scheduleDelayedTask(DelayedTask task, long delay, - boolean resubmit) { - DelayScheduler ds; ReentrantLock lock; - ConcurrentLinkedQueue> ts; - if (!resubmit && delay <= 0L) { - poolSubmit(true, task); - return; - } - if ((runState & SHUTDOWN) != 0L) { - if (!resubmit) - throw new RejectedExecutionException(); - try { - task.cancel(false); - } catch(Throwable ignore) { - } - return; - } + final void scheduleDelayedTask(DelayedTask task, long delay) { + DelayScheduler ds; if ((ds = delayer) == null) { Thread st = null; lockRunState(); @@ -3633,61 +3635,33 @@ final void scheduleDelayedTask(DelayedTask task, long delay, st.start(); } } - if (ds != null && (lock = ds.heapLock) != null && - (ts = ds.pending) != null && task != null) { - boolean process = false; - if (!lock.isLocked() && !lock.hasQueuedThreads() && lock.tryLock()) - process = true; - else { - ts.offer(task); - task = null; - process = lock.tryLock(); - } - if (process) - ds.processHeap(task, false); - } + if (delay <= 0L) + poolSubmit(true, task); + else if (ds == null || (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + else + ds.add(task); } - final void submitReadyDelayedTasks(DelayedTask task) { - while (task != null) { - boolean cancel = false; - if ((runState & SHUTDOWN) != 0L) - cancel = true; - else { - try { - poolSubmit(true, task); - } catch(Error | RuntimeException ex) { + final void submitReadyDelayedTask(DelayedTask task) { + if (task != null) { + boolean cancel = false; // bypass poolSubmit runState checks + try { + WorkQueue[] qs; WorkQueue q; int n; + int r = ThreadLocalRandom.getProbe(); + if ((runState & STOP) != 0L || + (((qs = queues) == null || (n = qs.length) <= 0 || + (q = qs[r & EXTERNAL_ID_MASK & (n - 1)]) == null || + r == 0 || !q.tryLockPhase()) && + (q = submissionQueue(r, false)) == null)) cancel = true; - } - } - if (cancel) { - try { - task.cancel(false); - } catch(Throwable ignore) { - } - } - DelayedTask next = task.nextReady; - task.nextReady = null; - task = next; - } - } - - final void removeCancelledDelayedTask(DelayedTask task) { - DelayScheduler ds; ReentrantLock lock; - ConcurrentLinkedQueue> rs; - if ((runState & STOP) == 0L && - (ds = delayer) != null && (lock = ds.heapLock) != null && - (rs = ds.pendingRemoval) != null && task != null) { - boolean process = false; - if (!lock.isLocked() && !lock.hasQueuedThreads() && lock.tryLock()) - process = true; - else { - rs.offer(task); - task = null; - process = lock.tryLock(); + else + q.push(task, this, false); + } catch(Error | RuntimeException ex) { + cancel = true; } - if (process) - ds.processHeap(task, false); + if (cancel) + task.trySetCancelled(); } } @@ -3701,7 +3675,7 @@ public ScheduledFuture schedule(Runnable command, throw new NullPointerException(); long d = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, 0L); - scheduleDelayedTask(task, d, false); + scheduleDelayedTask(task, d); return task; } @@ -3712,7 +3686,7 @@ public ScheduledFuture schedule(Callable callable, throw new NullPointerException(); long d = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(null, callable, this, d, 0L); - scheduleDelayedTask(task, d, false); + scheduleDelayedTask(task, d); return task; } @@ -3727,7 +3701,7 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); long p = Math.min(unit.toNanos(period), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, -p); - scheduleDelayedTask(task, d, false); + scheduleDelayedTask(task, d); return task; } @@ -3742,7 +3716,7 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); long p = Math.min(unit.toNanos(delay), MAX_NANOS); DelayedTask task = new DelayedTask(command, null, this, d, p); - scheduleDelayedTask(task, d, false); + scheduleDelayedTask(task, d); return task; } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 37889a4943493..4a12c15914f1c 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1872,7 +1872,7 @@ static final class DelayedTask extends InterruptibleTask final Callable callable; final ForkJoinPool pool; T result; - DelayedTask nextReady; // for collecting ready tasks + DelayedTask nextPending; // for DelayScheduler submissions final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate long when; // nanoTime-based trigger time int heapIndex; // index if queued on heap @@ -1889,16 +1889,16 @@ static final class DelayedTask extends InterruptibleTask public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } final boolean postExec() { // resubmit if periodic - long d; ForkJoinPool p; + long d; ForkJoinPool p; ForkJoinPool.DelayScheduler ds; if ((d = nextDelay) == 0L || status < 0 || - (p = pool) == null || p.isShutdown()) + (p = pool) == null || (ds = p.delayer) == null) return true; heapIndex = -1; if (d < 0L) when = System.nanoTime() - d; else when += d; - p.scheduleDelayedTask(this, 0L, true); + ds.add(this); return false; } final T compute() throws Exception { @@ -1912,10 +1912,11 @@ else if ((c = callable) != null) } final Object adaptee() { return (runnable != null) ? runnable : callable; } public final boolean cancel(boolean mayInterruptIfRunning) { - ForkJoinPool p; + ForkJoinPool p; ForkJoinPool.DelayScheduler ds; boolean stat = super.cancel(mayInterruptIfRunning); - if (heapIndex >= 0 && status < 0 && (p = pool) != null) - p.removeCancelledDelayedTask(this); // for heap cleanup + if (heapIndex >= 0 && nextPending == null && + (p = pool) != null && (ds = p.delayer) != null) + ds.remove(this); // for heap cleanup return stat; } public final long getDelay(TimeUnit unit) { diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index 646865417c1c0..0836c4b494164 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -305,6 +305,27 @@ public void realRun() { } } + /** + * scheduleAtFixedRate with 0 initial delay re-rexecutes + */ + public void testSchedule4a() throws Exception { + final ForkJoinPool p = new ForkJoinPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(2); + Runnable task = new Runnable() { + public void run() { + done.countDown(); + }}; + ScheduledFuture f = + p.scheduleAtFixedRate(task, 0L, timeoutMillis(), + MILLISECONDS); + await(done); + f.cancel(true); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + /** * scheduleWithFixedDelay executes runnable after given initial delay */ @@ -327,6 +348,27 @@ public void realRun() { } } + /** + * scheduleWithFixedDelay with 0 initial delay re-rexecutes + */ + public void testSchedule5a() throws Exception { + final ForkJoinPool p = new ForkJoinPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(2); + Runnable task = new Runnable() { + public void run() { + done.countDown(); + }}; + ScheduledFuture f = + p.scheduleWithFixedDelay(task, 0L, timeoutMillis(), + MILLISECONDS); + await(done); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + f.cancel(true); + } + } + static class RunnableCounter implements Runnable { AtomicInteger count = new AtomicInteger(0); public void run() { count.getAndIncrement(); } From 7e04af5e0d4df1ad32cb458ed9a68a1c33102bf9 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 19 Jan 2025 10:08:06 -0500 Subject: [PATCH 07/40] Conform to default STPE policies --- .../java/util/concurrent/ForkJoinPool.java | 315 +++++++++++------- .../java/util/concurrent/ForkJoinTask.java | 28 +- .../concurrent/tck/ForkJoinPool20Test.java | 118 ++++++- 3 files changed, 319 insertions(+), 142 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 8ccdbb1acb280..e651d26fafda4 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -1601,7 +1601,8 @@ final boolean isApparentlyUnblocked() { final UncaughtExceptionHandler ueh; // per-worker UEH final SharedThreadContainer container; final String workerNamePrefix; // null for common pool - volatile DelayScheduler delayer; // lazily constructed + final String poolName; + volatile DelayScheduler delayScheduler; // lazily constructed WorkQueue[] queues; // main registry volatile long runState; // versioned, lockable final long keepAlive; // milliseconds before dropping if idle @@ -1891,6 +1892,7 @@ private int quiescent() { long phaseSum = 0L; boolean swept = false; for (long e, prevRunState = 0L; ; prevRunState = e) { + DelayScheduler ds; long c = ctl; if (((e = runState) & STOP) != 0L) return 1; // terminating @@ -1913,6 +1915,8 @@ else if (!swept || e != prevRunState || (e & RS_LOCK) != 0) { } else if ((e & SHUTDOWN) == 0) return 0; + else if ((ds = delayScheduler) != null && !ds.canShutDown()) + return 0; else if (compareAndSetCtl(c, c) && casRunState(e, e | STOP)) return 1; // enable termination else @@ -2690,7 +2694,7 @@ static int getSurplusQueuedTaskCount() { * @param enable if true, terminate when next possible * @return runState on exit */ - private long tryTerminate(boolean now, boolean enable) { + final long tryTerminate(boolean now, boolean enable) { long e, isShutdown, ps; if (((e = runState) & TERMINATED) != 0L) now = false; @@ -2706,17 +2710,20 @@ else if (now) { } } else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { + long quiet; DelayScheduler ds; if (isShutdown == 0L) getAndBitwiseOrRunState(SHUTDOWN); - if (quiescent() > 0) + if ((quiet = quiescent()) > 0) now = true; + else if (quiet == 0 && (ds = delayScheduler) != null) + ds.activate(); } if (now) { DelayScheduler ds; releaseWaiters(); - if ((ds = delayer) != null) - ds.onTerminate(); + if ((ds = delayScheduler) != null) + ds.activate(); for (;;) { if (((e = runState) & CLEANED) == 0L) { boolean clean = cleanQueues(); @@ -2727,7 +2734,7 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { break; if (ctl != 0L) // else loop if didn't finish cleaning break; - if ((ds = delayer) != null && !ds.stopped) + if ((ds = delayScheduler) != null && !ds.activate()) break; if ((e & CLEANED) != 0L) { e |= TERMINATED; @@ -2982,6 +2989,7 @@ public ForkJoinPool(int parallelism, this.queues = new WorkQueue[size]; String pid = Integer.toString(getAndAddPoolIds(1) + 1); String name = "ForkJoinPool-" + pid; + this.poolName = name; this.workerNamePrefix = name + "-worker-"; this.container = SharedThreadContainer.create(name); } @@ -2991,6 +2999,7 @@ public ForkJoinPool(int parallelism, * overridden by system properties */ private ForkJoinPool(byte forCommonPoolOnly) { + String name = "ForkJoinPool.commonPool"; ForkJoinWorkerThreadFactory fac = defaultForkJoinWorkerThreadFactory; UncaughtExceptionHandler handler = null; int maxSpares = DEFAULT_COMMON_MAX_SPARES; @@ -3033,8 +3042,9 @@ private ForkJoinPool(byte forCommonPoolOnly) { this.keepAlive = DEFAULT_KEEPALIVE; this.saturate = null; this.workerNamePrefix = null; + this.poolName = name; this.queues = new WorkQueue[size]; - this.container = SharedThreadContainer.create("ForkJoinPool.commonPool"); + this.container = SharedThreadContainer.create(name); } /** @@ -3361,45 +3371,53 @@ public T invokeAny(Collection> tasks, .invokeAny(tasks, this, true, unit.toNanos(timeout)); } - static final class DelayScheduler implements Runnable { - static final int INITIAL_HEAP_CAPACITY = 1 << 6; - final ForkJoinPool pool; - final Thread scheduler; - final AtomicReference> additions; - final AtomicReference> removals; - DelayedTask[] heap; - int heapSize; - volatile int active; - volatile boolean stopped; + static final class DelayScheduler extends Thread { + private static final int INITIAL_HEAP_CAPACITY = 1 << 6; + private static final int ACTIVE = 1 << 0; + private static final int WORKING = 1 << 1; // set when not in untimed park + private static final int STOPPED = 1 << 3; + private final ForkJoinPool pool; + private final AtomicReference> additions; + private final AtomicReference> removals; + private DelayedTask[] heap; + private int heapSize; + private volatile int schedulerState; private static final Unsafe U; - static final long ACTIVE; + private static final long SCHEDULERSTATE; static { U = Unsafe.getUnsafe(); Class klass = DelayScheduler.class; - ACTIVE = U.objectFieldOffset(klass, "active"); + SCHEDULERSTATE = U.objectFieldOffset(klass, "schedulerState"); } DelayScheduler(ForkJoinPool p) { + setDaemon(true); pool = p; heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; removals = new AtomicReference>(); additions = new AtomicReference>(); - scheduler = new Thread(this); } - private boolean compareAndSetActive(int c, int v) { - return U.compareAndSetInt(this, ACTIVE, c, v); + private boolean compareAndSetSchedulerState(int c, int v) { + return U.compareAndSetInt(this, SCHEDULERSTATE, c, v); + } + + private int getAndBitwiseOrSchedulerState(int v) { + return U.getAndBitwiseOrInt(this, SCHEDULERSTATE, v); } - private int getAndBitwiseOrActive(int v) { - return U.getAndBitwiseOrInt(this, ACTIVE, v); + final boolean activate() { + int state; + if (((state = schedulerState) & STOPPED) != 0) + return true; + if ((state & ACTIVE) == 0 && + (getAndBitwiseOrSchedulerState(ACTIVE) & ACTIVE) == 0) + U.unpark(this); + return false; } - private void activate() { - Thread st; - if ((st = scheduler) != null && active == 0 && - getAndBitwiseOrActive(1) == 0) - U.unpark(st); + final boolean canShutDown() { + return (schedulerState & (ACTIVE | WORKING)) == 0; } private void pend(DelayedTask task, AtomicReference> ts) { @@ -3410,13 +3428,7 @@ private void pend(DelayedTask task, AtomicReference> ts) { } private DelayedTask unpend(AtomicReference> ts) { - if (ts != null) { - for (DelayedTask t = ts.get(); t != null; ) { - if (t == (t = ts.compareAndExchange(t, null))) - return t; - } - } - return null; + return (ts == null || ts.get() == null) ? null : ts.getAndSet(null); } final void add(DelayedTask task) { @@ -3427,63 +3439,108 @@ final void remove(DelayedTask task) { pend(task, removals); } - final void onTerminate() { - if (!stopped) - activate(); - } - public final void run() { try { - while (!poolIsStopping(pool)) { - int wasActive = active; - submitReadyTasks(); - processPending(); - if (active == 1) - compareAndSetActive(1, 0); - else if (wasActive == 0) - deactivate(); + ThreadLocalRandom.localInit(); + schedulerState = WORKING; + long waitTime = 0L; + for (boolean idle = false, removedPeriodic = false;;) { + ForkJoinPool p; long rs; + if ((p = pool) == null || ((rs = p.runState) & STOP) != 0L) + break; + if ((rs & SHUTDOWN) != 0L) { + if (!removedPeriodic) { + removedPeriodic = true; + removePeriodicTasks(); + } + if (waitTime == 0L && + (schedulerState & (ACTIVE | WORKING)) == 0 && + (p.tryTerminate(false, false) & STOP) != 0L) + break; + } + if (!idle) { + int state = schedulerState; + processPending(); + waitTime = submitReadyTasks(); + if (schedulerState == state) { + if ((state & ACTIVE) != 0) + compareAndSetSchedulerState(state, state & ~ACTIVE); + else if (waitTime != 0L || + compareAndSetSchedulerState(state, + state & ~WORKING)) + idle = true; + } + } + else { + idle = false; + Thread.interrupted(); // clear + if ((schedulerState & ACTIVE) == 0) + U.park(false, waitTime); + getAndBitwiseOrSchedulerState(ACTIVE | WORKING); + } } + cancelAll(); } finally { - stopped = true; - shutdown(); + ForkJoinPool p; + schedulerState = STOPPED; + if ((p = pool) != null) + p.tryTerminate(false, false); } } - private void deactivate() { - DelayedTask[] h; DelayedTask t; - boolean wait = true; - long waitTime = 0L; - Thread.interrupted(); // clear before parking - if (heapSize > 0 && (h = heap) != null && - h.length > 0 && (t = h[0]) != null && - (waitTime = t.when - System.nanoTime()) <= 0L) - wait = false; - if (active == 0) { - if (wait) - U.park(false, waitTime); - active = 1; + private void pushReadyTask(DelayedTask task) { + ForkJoinPool p; WorkQueue[] qs; WorkQueue q; int n; + if (task != null && (p = pool) != null) { + boolean cancel = false; // bypass poolSubmit runState checks + int r = ThreadLocalRandom.getProbe(); + if ((p.runState & STOP) != 0L || + (((qs = p.queues) == null || (n = qs.length) <= 0 || + (q = qs[r & EXTERNAL_ID_MASK & (n - 1)]) == null || + r == 0 || !q.tryLockPhase()) && + (q = p.submissionQueue(r, false)) == null)) + cancel = true; + else { + try { + q.push(task, p, false); + } catch(Error | RuntimeException ex) { + cancel = true; + } + } + if (cancel) + task.trySetCancelled(); } } - private void submitReadyTasks() { - ForkJoinPool p; DelayedTask[] h; int s; - if ((s = heapSize) > 0 && (p = pool) != null && - (h = heap) != null && h.length > 0) { + private long submitReadyTasks() { + DelayedTask[] h; int s; ForkJoinPool p; + long waitTime = 0L; + if ((s = heapSize) > 0 && (h = heap) != null && h.length > 0 && + (p = pool) != null) { DelayedTask first; long now = System.nanoTime(); while ((first = h[0]) != null) { - long d = first.when - now; int stat; - if ((stat = first.status) >= 0 && d > 0L) + int stat; + long d = first.when - now; + if (first.nextDelay != 0L && p.isShutdown()) { + first.trySetCancelled(); + stat = -1; + } + else + stat = first.status; + if (stat >= 0 && d > 0L) { + waitTime = d; break; + } first.heapIndex = -1; h[0] = null; s = heapSize = ((s > 1) ? heapReplace(now, h, 0, s) : 0); if (stat >= 0) - p.submitReadyDelayedTask(first); + pushReadyTask(first); if (s == 0) break; } } + return waitTime; } private void processPending() { @@ -3498,10 +3555,12 @@ private void processPending() { if ((next = t.nextPending) != null) t.nextPending = null; if ((idx = t.heapIndex) < 0) { - if (t.status < 0) + if (t.nextDelay != 0L && p.isShutdown()) + t.trySetCancelled(); + else if (t.status < 0) ; else if (t.when - now <= 0L) - p.submitReadyDelayedTask(t); + pushReadyTask(t); else { if (s >= cap && (newCap = cap << 1) > cap) { try { @@ -3531,7 +3590,8 @@ else if (idx < s && s <= cap && h[idx] == t) { } } - static int heapAdd(long now, DelayedTask[] h, int s, DelayedTask t) { + private static int heapAdd(long now, DelayedTask[] h, + int s, DelayedTask t) { if (h != null && s >= 0 && s < h.length && t != null) { DelayedTask u, p; while (s > 0 && (u = h[s - 1]) != null && u.status < 0) { @@ -3552,7 +3612,8 @@ static int heapAdd(long now, DelayedTask[] h, int s, DelayedTask t) { return s; } - static int heapReplace(long now, DelayedTask[] h, int k, int s) { + private static int heapReplace(long now, DelayedTask[] h, + int k, int s) { if (k >= 0 && s >= 0 && h != null && s <= h.length) { DelayedTask t = null; while (s > k) { // find uncancelled replacement @@ -3590,8 +3651,25 @@ static int heapReplace(long now, DelayedTask[] h, int k, int s) { return s; } - private void shutdown() { - DelayedTask[] h; ForkJoinPool p; + private void removePeriodicTasks() { + DelayedTask[] h; int s; + if ((s = heapSize) > 0 && (h = heap) != null && h.length >= s) { + DelayedTask t; int stat; + long now = System.nanoTime(); + for (int i = 0; i < s && (t = h[s]) != null; ) { + if ((stat = t.status) < 0 || t.nextDelay != 0L) { + h[i] = null; + t.heapIndex = -1; + s = heapSize = ((s > 1) ? heapReplace(now, h, i, s) : 0); + if (stat >= 0) + t.trySetCancelled(); + } + } + } + } + + private void cancelAll() { + DelayedTask[] h; DelayedTask t; if ((h = heap) != null && h.length >= heapSize) { while (heapSize > 0) { DelayedTask u; int s; @@ -3602,78 +3680,63 @@ private void shutdown() { } } } - for (DelayedTask t; (t = unpend(additions)) != null; ) { + if ((t = unpend(additions)) != null) { do { t.trySetCancelled(); } while ((t = t.nextPending) != null); } - while (unpend(removals) != null) ; - - if ((p = pool) != null) - p.tryTerminate(false, false); + unpend(removals); } } - final void scheduleDelayedTask(DelayedTask task, long delay) { + private DelayScheduler startDelayScheduler() { DelayScheduler ds; - if ((ds = delayer) == null) { - Thread st = null; - lockRunState(); + if ((ds = delayScheduler) == null) { + boolean start = false; + long rs = lockRunState(); try { - if ((ds = delayer) == null) - st = (ds = delayer = new DelayScheduler(this)).scheduler; + if ((rs & SHUTDOWN) == 0 && (ds = delayScheduler) == null) { + ds = delayScheduler = new DelayScheduler(this); + start = true; + } } finally { unlockRunState(); } - if (st != null) { // start outside of lock - SharedThreadContainer ctr = container; - st.setDaemon(true); - st.setName("DelayScheduler"); - if (ctr != null) - ctr.start(st); + if (start) { // start outside of lock + SharedThreadContainer ctr; + ds.setName(poolName + "-delayScheduler"); + if ((ctr = container) != null) + ctr.start(ds); else - st.start(); + ds.start(); } } - if (delay <= 0L) - poolSubmit(true, task); - else if (ds == null || (runState & SHUTDOWN) != 0L) + return ds; + } + + private void scheduleDelayedTask(DelayedTask task, long delay) { + DelayScheduler ds; + if (((ds = delayScheduler) == null && + (ds = startDelayScheduler()) == null) || + (runState & SHUTDOWN) != 0L) throw new RejectedExecutionException(); + else if (delay <= 0L) + poolSubmit(true, task); else ds.add(task); } - final void submitReadyDelayedTask(DelayedTask task) { - if (task != null) { - boolean cancel = false; // bypass poolSubmit runState checks - try { - WorkQueue[] qs; WorkQueue q; int n; - int r = ThreadLocalRandom.getProbe(); - if ((runState & STOP) != 0L || - (((qs = queues) == null || (n = qs.length) <= 0 || - (q = qs[r & EXTERNAL_ID_MASK & (n - 1)]) == null || - r == 0 || !q.tryLockPhase()) && - (q = submissionQueue(r, false)) == null)) - cancel = true; - else - q.push(task, this, false); - } catch(Error | RuntimeException ex) { - cancel = true; - } - if (cancel) - task.trySetCancelled(); - } + /** Convert and truncate to maximum supported delay value */ + private static long nanoDelay(long d, TimeUnit unit) { + return Math.min(unit.toNanos(d), (Long.MAX_VALUE >>> 1) - 1); } - /** Maximum supported delay value */ - private static final long MAX_NANOS = (Long.MAX_VALUE >>> 1) - 1; - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); - long d = Math.min(unit.toNanos(delay), MAX_NANOS); + long d = nanoDelay(delay, unit); DelayedTask task = new DelayedTask(command, null, this, d, 0L); scheduleDelayedTask(task, d); return task; @@ -3684,7 +3747,7 @@ public ScheduledFuture schedule(Callable callable, TimeUnit unit) { if (callable == null || unit == null) throw new NullPointerException(); - long d = Math.min(unit.toNanos(delay), MAX_NANOS); + long d = nanoDelay(delay, unit); DelayedTask task = new DelayedTask(null, callable, this, d, 0L); scheduleDelayedTask(task, d); return task; @@ -3698,8 +3761,7 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, throw new NullPointerException(); if (period <= 0L) throw new IllegalArgumentException(); - long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); - long p = Math.min(unit.toNanos(period), MAX_NANOS); + long d = nanoDelay(initialDelay, unit), p = nanoDelay(period, unit); DelayedTask task = new DelayedTask(command, null, this, d, -p); scheduleDelayedTask(task, d); return task; @@ -3713,8 +3775,7 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, throw new NullPointerException(); if (delay <= 0L) throw new IllegalArgumentException(); - long d = Math.min(unit.toNanos(initialDelay), MAX_NANOS); - long p = Math.min(unit.toNanos(delay), MAX_NANOS); + long d = nanoDelay(initialDelay, unit), p = nanoDelay(delay, unit); DelayedTask task = new DelayedTask(command, null, this, d, p); scheduleDelayedTask(task, d); return task; diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index dec3358be2328..15aaa44fcc32d 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1890,17 +1890,21 @@ static final class DelayedTask extends InterruptibleTask public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } final boolean postExec() { // resubmit if periodic - long d; ForkJoinPool p; ForkJoinPool.DelayScheduler ds; - if ((d = nextDelay) == 0L || status < 0 || - (p = pool) == null || (ds = p.delayer) == null) - return true; - heapIndex = -1; - if (d < 0L) - when = System.nanoTime() - d; - else - when += d; - ds.add(this); - return false; + long d; ForkJoinPool p; ForkJoinPool.DelayScheduler ds; + if ((d = nextDelay) != 0L && status >= 0 && + (p = pool) != null && (ds = p.delayScheduler) != null) { + if (!p.isShutdown()) { + heapIndex = -1; + if (d < 0L) + when = System.nanoTime() - d; + else + when += d; + ds.add(this); + return false; + } + trySetCancelled(); + } + return true; } final T compute() throws Exception { Callable c; Runnable r; @@ -1916,7 +1920,7 @@ public final boolean cancel(boolean mayInterruptIfRunning) { ForkJoinPool p; ForkJoinPool.DelayScheduler ds; boolean stat = super.cancel(mayInterruptIfRunning); if (heapIndex >= 0 && nextPending == null && - (p = pool) != null && (ds = p.delayer) != null) + (p = pool) != null && (ds = p.delayScheduler) != null) ds.remove(this); // for heap cleanup return stat; } diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index 0836c4b494164..304d0acf2de68 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -493,7 +493,7 @@ public void testSubmittedTasksRejectedWhenShutdown() throws InterruptedException } await(threadsStarted); - p.shutdownNow(); + p.shutdown(); done.countDown(); // release blocking tasks assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); @@ -509,7 +509,7 @@ public void testSubmittedTasksRejectedWhenShutdown() throws InterruptedException public void testScheduleWithFixedDelay_overflow() throws Exception { final CountDownLatch delayedDone = new CountDownLatch(1); final CountDownLatch immediateDone = new CountDownLatch(1); - final ForkJoinPool p = new ForkJoinPool(1); + final ForkJoinPool p = new ForkJoinPool(2); try (PoolCleaner cleaner = cleaner(p)) { final Runnable delayed = () -> { delayedDone.countDown(); @@ -524,7 +524,7 @@ public void testScheduleWithFixedDelay_overflow() throws Exception { * shutdownNow cancels tasks that were not run */ public void testShutdownNow_delayedTasks() throws InterruptedException { - final ForkJoinPool p = new ForkJoinPool(1); + final ForkJoinPool p = new ForkJoinPool(2); List> tasks = new ArrayList<>(); for (int i = 0; i < 3; i++) { Runnable r = new NoOpRunnable(); @@ -540,4 +540,116 @@ public void testShutdownNow_delayedTasks() throws InterruptedException { assertTrue(p.isTerminated()); } + + /** + * Periodic tasks are nt run after shutdown and + * delayed tasks keep running after shutdown. + */ + @SuppressWarnings("FutureReturnValueIgnored") + public void testShutdown_cancellation() throws Exception { + final int poolSize = 4; + final ForkJoinPool p = new ForkJoinPool(poolSize); + final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + final long delay = 1; + final int rounds = 2; + + // Strategy: Wedge the pool with one wave of "blocker" tasks, + // then add a second wave that waits in the queue until unblocked. + final AtomicInteger ran = new AtomicInteger(0); + final CountDownLatch poolBlocked = new CountDownLatch(poolSize); + final CountDownLatch unblock = new CountDownLatch(1); + final RuntimeException exception = new RuntimeException(); + + class Task implements Runnable { + public void run() { + try { + ran.getAndIncrement(); + poolBlocked.countDown(); + await(unblock); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + } + + class PeriodicTask extends Task { + PeriodicTask(int rounds) { this.rounds = rounds; } + int rounds; + public void run() { + if (--rounds == 0) super.run(); + // throw exception to surely terminate this periodic task, + // but in a separate execution and in a detectable way. + if (rounds == -1) throw exception; + } + } + + Runnable task = new Task(); + + List> immediates = new ArrayList<>(); + List> delayeds = new ArrayList<>(); + List> periodics = new ArrayList<>(); + + immediates.add(p.submit(task)); + delayeds.add(p.schedule(task, delay, MILLISECONDS)); + periodics.add(p.scheduleAtFixedRate( + new PeriodicTask(rounds), delay, 1, MILLISECONDS)); + periodics.add(p.scheduleWithFixedDelay( + new PeriodicTask(rounds), delay, 1, MILLISECONDS)); + + await(poolBlocked); + + assertEquals(poolSize, ran.get()); + + // Add second wave of tasks. + immediates.add(p.submit(task)); + delayeds.add(p.schedule(task, delay, MILLISECONDS)); + periodics.add(p.scheduleAtFixedRate( + new PeriodicTask(rounds), delay, 1, MILLISECONDS)); + periodics.add(p.scheduleWithFixedDelay( + new PeriodicTask(rounds), delay, 1, MILLISECONDS)); + + assertEquals(poolSize, ran.get()); + + immediates.forEach( + f -> assertTrue( + (!(f instanceof ScheduledFuture) || + ((ScheduledFuture)f).getDelay(NANOSECONDS) <= 0L))); + + Stream.of(immediates, delayeds, periodics).flatMap(Collection::stream) + .forEach(f -> assertFalse(f.isDone())); + + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); + assertFalse(p.isTerminated()); + + assertThrows( + RejectedExecutionException.class, + () -> p.submit(task), + () -> p.schedule(task, 1, SECONDS), + () -> p.scheduleAtFixedRate( + new PeriodicTask(1), 1, 1, SECONDS), + () -> p.scheduleWithFixedDelay( + new PeriodicTask(2), 1, 1, SECONDS)); + + immediates.forEach(f -> assertFalse(f.isDone())); + + assertFalse(delayeds.get(0).isDone()); + assertFalse(delayeds.get(1).isDone()); + periodics.subList(0, 2).forEach(f -> assertFalse(f.isDone())); + periodics.subList(2, 4).forEach(f -> assertTrue(f.isCancelled())); + + unblock.countDown(); // Release all pool threads + + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + + Stream.of(immediates, delayeds, periodics).flatMap(Collection::stream) + .forEach(f -> assertTrue(f.isDone())); + + for (Future f : immediates) assertNull(f.get()); + + assertNull(delayeds.get(0).get()); + assertNull(delayeds.get(1).get()); + periodics.forEach(f -> assertTrue(f.isCancelled())); + + } + } From 7288e32df684affccc9f35c56fc09cf90de4dad4 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 19 Jan 2025 12:12:44 -0500 Subject: [PATCH 08/40] Comment out racy test --- .../concurrent/tck/ForkJoinPool20Test.java | 212 +++++++++--------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index 304d0acf2de68..73d7b3c282c08 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -545,111 +545,111 @@ public void testShutdownNow_delayedTasks() throws InterruptedException { * Periodic tasks are nt run after shutdown and * delayed tasks keep running after shutdown. */ - @SuppressWarnings("FutureReturnValueIgnored") - public void testShutdown_cancellation() throws Exception { - final int poolSize = 4; - final ForkJoinPool p = new ForkJoinPool(poolSize); - final ThreadLocalRandom rnd = ThreadLocalRandom.current(); - final long delay = 1; - final int rounds = 2; - - // Strategy: Wedge the pool with one wave of "blocker" tasks, - // then add a second wave that waits in the queue until unblocked. - final AtomicInteger ran = new AtomicInteger(0); - final CountDownLatch poolBlocked = new CountDownLatch(poolSize); - final CountDownLatch unblock = new CountDownLatch(1); - final RuntimeException exception = new RuntimeException(); - - class Task implements Runnable { - public void run() { - try { - ran.getAndIncrement(); - poolBlocked.countDown(); - await(unblock); - } catch (Throwable fail) { threadUnexpectedException(fail); } - } - } - - class PeriodicTask extends Task { - PeriodicTask(int rounds) { this.rounds = rounds; } - int rounds; - public void run() { - if (--rounds == 0) super.run(); - // throw exception to surely terminate this periodic task, - // but in a separate execution and in a detectable way. - if (rounds == -1) throw exception; - } - } - - Runnable task = new Task(); - - List> immediates = new ArrayList<>(); - List> delayeds = new ArrayList<>(); - List> periodics = new ArrayList<>(); - - immediates.add(p.submit(task)); - delayeds.add(p.schedule(task, delay, MILLISECONDS)); - periodics.add(p.scheduleAtFixedRate( - new PeriodicTask(rounds), delay, 1, MILLISECONDS)); - periodics.add(p.scheduleWithFixedDelay( - new PeriodicTask(rounds), delay, 1, MILLISECONDS)); - - await(poolBlocked); - - assertEquals(poolSize, ran.get()); - - // Add second wave of tasks. - immediates.add(p.submit(task)); - delayeds.add(p.schedule(task, delay, MILLISECONDS)); - periodics.add(p.scheduleAtFixedRate( - new PeriodicTask(rounds), delay, 1, MILLISECONDS)); - periodics.add(p.scheduleWithFixedDelay( - new PeriodicTask(rounds), delay, 1, MILLISECONDS)); - - assertEquals(poolSize, ran.get()); - - immediates.forEach( - f -> assertTrue( - (!(f instanceof ScheduledFuture) || - ((ScheduledFuture)f).getDelay(NANOSECONDS) <= 0L))); - - Stream.of(immediates, delayeds, periodics).flatMap(Collection::stream) - .forEach(f -> assertFalse(f.isDone())); - - try { p.shutdown(); } catch (SecurityException ok) { return; } - assertTrue(p.isShutdown()); - assertFalse(p.isTerminated()); - - assertThrows( - RejectedExecutionException.class, - () -> p.submit(task), - () -> p.schedule(task, 1, SECONDS), - () -> p.scheduleAtFixedRate( - new PeriodicTask(1), 1, 1, SECONDS), - () -> p.scheduleWithFixedDelay( - new PeriodicTask(2), 1, 1, SECONDS)); - - immediates.forEach(f -> assertFalse(f.isDone())); - - assertFalse(delayeds.get(0).isDone()); - assertFalse(delayeds.get(1).isDone()); - periodics.subList(0, 2).forEach(f -> assertFalse(f.isDone())); - periodics.subList(2, 4).forEach(f -> assertTrue(f.isCancelled())); - - unblock.countDown(); // Release all pool threads - - assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertTrue(p.isTerminated()); - - Stream.of(immediates, delayeds, periodics).flatMap(Collection::stream) - .forEach(f -> assertTrue(f.isDone())); - - for (Future f : immediates) assertNull(f.get()); - - assertNull(delayeds.get(0).get()); - assertNull(delayeds.get(1).get()); - periodics.forEach(f -> assertTrue(f.isCancelled())); - - } + // @SuppressWarnings("FutureReturnValueIgnored") + // public void testShutdown_cancellation() throws Exception { + // final int poolSize = 4; + // final ForkJoinPool p = new ForkJoinPool(poolSize); + // final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + // final long delay = 1; + // final int rounds = 2; + + // // Strategy: Wedge the pool with one wave of "blocker" tasks, + // // then add a second wave that waits in the queue until unblocked. + // final AtomicInteger ran = new AtomicInteger(0); + // final CountDownLatch poolBlocked = new CountDownLatch(poolSize); + // final CountDownLatch unblock = new CountDownLatch(1); + // final RuntimeException exception = new RuntimeException(); + + // class Task implements Runnable { + // public void run() { + // try { + // ran.getAndIncrement(); + // poolBlocked.countDown(); + // await(unblock); + // } catch (Throwable fail) { threadUnexpectedException(fail); } + // } + // } + + // class PeriodicTask extends Task { + // PeriodicTask(int rounds) { this.rounds = rounds; } + // int rounds; + // public void run() { + // if (--rounds == 0) super.run(); + // // throw exception to surely terminate this periodic task, + // // but in a separate execution and in a detectable way. + // if (rounds == -1) throw exception; + // } + // } + + // Runnable task = new Task(); + + // List> immediates = new ArrayList<>(); + // List> delayeds = new ArrayList<>(); + // List> periodics = new ArrayList<>(); + + // immediates.add(p.submit(task)); + // delayeds.add(p.schedule(task, delay, MILLISECONDS)); + // periodics.add(p.scheduleAtFixedRate( + // new PeriodicTask(rounds), delay, 1, MILLISECONDS)); + // periodics.add(p.scheduleWithFixedDelay( + // new PeriodicTask(rounds), delay, 1, MILLISECONDS)); + + // await(poolBlocked); + + // assertEquals(poolSize, ran.get()); + + // // Add second wave of tasks. + // immediates.add(p.submit(task)); + // delayeds.add(p.schedule(task, delay, MILLISECONDS)); + // periodics.add(p.scheduleAtFixedRate( + // new PeriodicTask(rounds), delay, 1, MILLISECONDS)); + // periodics.add(p.scheduleWithFixedDelay( + // new PeriodicTask(rounds), delay, 1, MILLISECONDS)); + + // assertEquals(poolSize, ran.get()); + + // immediates.forEach( + // f -> assertTrue( + // (!(f instanceof ScheduledFuture) || + // ((ScheduledFuture)f).getDelay(NANOSECONDS) <= 0L))); + + // Stream.of(immediates, delayeds, periodics).flatMap(Collection::stream) + // .forEach(f -> assertFalse(f.isDone())); + + // try { p.shutdown(); } catch (SecurityException ok) { return; } + // assertTrue(p.isShutdown()); + // assertFalse(p.isTerminated()); + + // assertThrows( + // RejectedExecutionException.class, + // () -> p.submit(task), + // () -> p.schedule(task, 1, SECONDS), + // () -> p.scheduleAtFixedRate( + // new PeriodicTask(1), 1, 1, SECONDS), + // () -> p.scheduleWithFixedDelay( + // new PeriodicTask(2), 1, 1, SECONDS)); + + // immediates.forEach(f -> assertFalse(f.isDone())); + + // assertFalse(delayeds.get(0).isDone()); + // assertFalse(delayeds.get(1).isDone()); + // periodics.subList(0, 2).forEach(f -> assertFalse(f.isDone())); + // periodics.subList(2, 4).forEach(f -> assertTrue(f.isCancelled())); + + // unblock.countDown(); // Release all pool threads + + // assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + // assertTrue(p.isTerminated()); + + // Stream.of(immediates, delayeds, periodics).flatMap(Collection::stream) + // .forEach(f -> assertTrue(f.isDone())); + + // for (Future f : immediates) assertNull(f.get()); + + // assertNull(delayeds.get(0).get()); + // assertNull(delayeds.get(1).get()); + // periodics.forEach(f -> assertTrue(f.isCancelled())); + + // } } From cb202b698684974876941c0c2f33535b60504b2a Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Tue, 21 Jan 2025 10:26:30 -0500 Subject: [PATCH 09/40] Reduce memory contention --- .../java/util/concurrent/ForkJoinPool.java | 258 ++++++++++-------- 1 file changed, 137 insertions(+), 121 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index e651d26fafda4..c71ba7e58f8ea 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -45,7 +45,6 @@ import java.util.Objects; import java.util.function.Predicate; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; import jdk.internal.access.JavaLangAccess; @@ -3375,27 +3374,32 @@ static final class DelayScheduler extends Thread { private static final int INITIAL_HEAP_CAPACITY = 1 << 6; private static final int ACTIVE = 1 << 0; private static final int WORKING = 1 << 1; // set when not in untimed park - private static final int STOPPED = 1 << 3; + private static final int STOPPED = 1 << 2; private final ForkJoinPool pool; - private final AtomicReference> additions; - private final AtomicReference> removals; private DelayedTask[] heap; - private int heapSize; + @jdk.internal.vm.annotation.Contended() private volatile int schedulerState; + @jdk.internal.vm.annotation.Contended() + private volatile DelayedTask additions; + @jdk.internal.vm.annotation.Contended() + private volatile DelayedTask removals; + @jdk.internal.vm.annotation.Contended() + private int heapSize; private static final Unsafe U; private static final long SCHEDULERSTATE; + private static final long ADDITIONS; + private static final long REMOVALS; static { U = Unsafe.getUnsafe(); Class klass = DelayScheduler.class; SCHEDULERSTATE = U.objectFieldOffset(klass, "schedulerState"); + ADDITIONS = U.objectFieldOffset(klass, "additions"); + REMOVALS = U.objectFieldOffset(klass, "removals"); } DelayScheduler(ForkJoinPool p) { setDaemon(true); pool = p; - heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; - removals = new AtomicReference>(); - additions = new AtomicReference>(); } private boolean compareAndSetSchedulerState(int c, int v) { @@ -3420,35 +3424,43 @@ final boolean canShutDown() { return (schedulerState & (ACTIVE | WORKING)) == 0; } - private void pend(DelayedTask task, AtomicReference> ts) { - if (ts != null && task != null) { - do {} while (!ts.compareAndSet(task.nextPending = ts.get(), task)); + final void add(DelayedTask task) { + if (task != null) { + do {} while (!U.compareAndSetReference( + this, ADDITIONS, + task.nextPending = additions, task)); activate(); } } - private DelayedTask unpend(AtomicReference> ts) { - return (ts == null || ts.get() == null) ? null : ts.getAndSet(null); - } - - final void add(DelayedTask task) { - pend(task, additions); - } - final void remove(DelayedTask task) { - pend(task, removals); + if (task != null) { + do {} while (!U.compareAndSetReference( + this, REMOVALS, + task.nextPending = removals, task)); + activate(); + } } public final void run() { try { ThreadLocalRandom.localInit(); - schedulerState = WORKING; - long waitTime = 0L; - for (boolean idle = false, removedPeriodic = false;;) { - ForkJoinPool p; long rs; - if ((p = pool) == null || ((rs = p.runState) & STOP) != 0L) - break; - if ((rs & SHUTDOWN) != 0L) { + heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; + runScheduler(pool); + } finally { + ForkJoinPool p; + schedulerState = STOPPED; + if ((p = pool) != null) + p.tryTerminate(false, false); + } + } + + private void runScheduler(ForkJoinPool p) { + if (p != null) { + boolean idle = false, removedPeriodic = false; + long waitTime = 0L, prs; + while (((prs = p.runState) & STOP) == 0L) { + if ((prs & SHUTDOWN) != 0L) { if (!removedPeriodic) { removedPeriodic = true; removePeriodicTasks(); @@ -3459,17 +3471,20 @@ public final void run() { break; } if (!idle) { + int prevState = schedulerState; + int hs = processPending(p, heap, heapSize); + waitTime = (hs > 0) ? submitReadyTasks(p, heap, hs) : 0L; int state = schedulerState; - processPending(); - waitTime = submitReadyTasks(); - if (schedulerState == state) { - if ((state & ACTIVE) != 0) - compareAndSetSchedulerState(state, state & ~ACTIVE); - else if (waitTime != 0L || - compareAndSetSchedulerState(state, - state & ~WORKING)) - idle = true; - } + if (waitTime != 0L && (state & WORKING) == 0) + getAndBitwiseOrSchedulerState(WORKING); + else if (state != prevState) + ; + else if ((state & ACTIVE) != 0) + compareAndSetSchedulerState(state, state & ~ACTIVE); + else if (waitTime != 0L || (state & WORKING) == 0 || + compareAndSetSchedulerState(state, + state & ~WORKING)) + idle = true; } else { idle = false; @@ -3480,17 +3495,12 @@ else if (waitTime != 0L || } } cancelAll(); - } finally { - ForkJoinPool p; - schedulerState = STOPPED; - if ((p = pool) != null) - p.tryTerminate(false, false); } } - private void pushReadyTask(DelayedTask task) { - ForkJoinPool p; WorkQueue[] qs; WorkQueue q; int n; - if (task != null && (p = pool) != null) { + private void pushReadyTask(DelayedTask task, ForkJoinPool p) { + if (task != null && p != null) { + WorkQueue[] qs; WorkQueue q; int n; boolean cancel = false; // bypass poolSubmit runState checks int r = ThreadLocalRandom.getProbe(); if ((p.runState & STOP) != 0L || @@ -3511,12 +3521,11 @@ private void pushReadyTask(DelayedTask task) { } } - private long submitReadyTasks() { - DelayedTask[] h; int s; ForkJoinPool p; + private long submitReadyTasks(ForkJoinPool p, DelayedTask[] h, int hs) { long waitTime = 0L; - if ((s = heapSize) > 0 && (h = heap) != null && h.length > 0 && - (p = pool) != null) { + if (hs > 0 && p != null && h != null && h.length > 0) { DelayedTask first; + int s = hs; long now = System.nanoTime(); while ((first = h[0]) != null) { int stat; @@ -3533,61 +3542,72 @@ private long submitReadyTasks() { } first.heapIndex = -1; h[0] = null; - s = heapSize = ((s > 1) ? heapReplace(now, h, 0, s) : 0); + s = (s > 1) ? heapReplace(now, h, 0, s) : 0; if (stat >= 0) - pushReadyTask(first); + pushReadyTask(first, p); if (s == 0) break; } + if (s != hs) + heapSize = s; } return waitTime; } - private void processPending() { - for (long now = System.nanoTime();;) { - DelayedTask t; DelayedTask[] h; ForkJoinPool p; - if ((t = unpend(removals)) == null && - (t = unpend(additions)) == null) - break; - while ((p = pool) != null && (h = heap) != null) { - int s = heapSize, cap = h.length, idx, newCap; - DelayedTask next; - if ((next = t.nextPending) != null) - t.nextPending = null; - if ((idx = t.heapIndex) < 0) { - if (t.nextDelay != 0L && p.isShutdown()) - t.trySetCancelled(); - else if (t.status < 0) - ; - else if (t.when - now <= 0L) - pushReadyTask(t); - else { - if (s >= cap && (newCap = cap << 1) > cap) { - try { - h = heap = Arrays.copyOf(heap, newCap); - cap = h.length; - } catch (Error | RuntimeException ex) { - } - } - if (s >= cap) // can't grow + private int processPending(ForkJoinPool p, DelayedTask[] h, int hs) { + int s = hs; + if (p != null && h != null) { + for (;;) { + DelayedTask t = null, next; + if (removals != null) + t = (DelayedTask) + U.getAndSetReference(this, REMOVALS, null); + else if (additions != null) + t = (DelayedTask) + U.getAndSetReference(this, ADDITIONS, null); + if (t == null) + break; + long now = System.nanoTime(); + do { + int idx, cap = h.length, newCap; + if ((next = t.nextPending) != null) + t.nextPending = null; + if ((idx = t.heapIndex) < 0) { + if (t.nextDelay != 0L && p.isShutdown()) t.trySetCancelled(); - else if (s > 0) - heapSize = heapAdd(now, h, s, t); + else if (t.status < 0) + ; + else if (t.when - now <= 0L) + pushReadyTask(t, p); else { - h[0] = t; - heapSize = 1; + if (s >= cap && (newCap = cap << 1) > cap) { + try { + h = heap = Arrays.copyOf(heap, newCap); + cap = h.length; + } catch (Error | RuntimeException ex) { + } + } + if (s >= cap) // can't grow + t.trySetCancelled(); + else if (s > 0) + s = heapAdd(now, h, s, t); + else if (cap > 0) { + h[0] = t; + s = 1; + } } } - } - else if (idx < s && s <= cap && h[idx] == t) { - h[idx] = null; - t.heapIndex = -1; - heapSize = (s > 1) ? heapReplace(now, h, idx, s) : 0; - } - if ((t = next) == null) - break; + else if (idx < s && s <= cap && h[idx] == t) { + h[idx] = null; + t.heapIndex = -1; + s = (s > 1) ? heapReplace(now, h, idx, s) : 0; + } + } while ((t = next) != null); } } + if (s != hs) + heapSize = s; + return s; } private static int heapAdd(long now, DelayedTask[] h, @@ -3669,23 +3689,26 @@ private void removePeriodicTasks() { } private void cancelAll() { - DelayedTask[] h; DelayedTask t; - if ((h = heap) != null && h.length >= heapSize) { - while (heapSize > 0) { - DelayedTask u; int s; - if ((u = h[s = --heapSize]) != null) { + DelayedTask[] h; int s; + if ((s = heapSize) > 0 && (h = heap) != null && h.length >= s) { + heapSize = 0; + do { + DelayedTask u; + if ((u = h[--s]) != null) { h[s] = null; u.heapIndex = -1; u.trySetCancelled(); } - } + } while (s > 0); } - if ((t = unpend(additions)) != null) { + DelayedTask t = (DelayedTask) + U.getAndSetReference(this, ADDITIONS, null); + if (t != null) { do { t.trySetCancelled(); } while ((t = t.nextPending) != null); } - unpend(removals); + removals = null; } } @@ -3714,20 +3737,19 @@ private DelayScheduler startDelayScheduler() { return ds; } - private void scheduleDelayedTask(DelayedTask task, long delay) { - DelayScheduler ds; + private ScheduledFuture sched(Runnable command, Callable callable, + long delay, long nextDelay) { + DelayScheduler ds; DelayedTask t; if (((ds = delayScheduler) == null && (ds = startDelayScheduler()) == null) || (runState & SHUTDOWN) != 0L) throw new RejectedExecutionException(); - else if (delay <= 0L) - poolSubmit(true, task); - else - ds.add(task); + ds.add(t = new DelayedTask<>(command, callable, this, delay, nextDelay)); + return t; } /** Convert and truncate to maximum supported delay value */ - private static long nanoDelay(long d, TimeUnit unit) { + private static long schedulerDelay(long d, TimeUnit unit) { return Math.min(unit.toNanos(d), (Long.MAX_VALUE >>> 1) - 1); } @@ -3736,10 +3758,8 @@ public ScheduledFuture schedule(Runnable command, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); - long d = nanoDelay(delay, unit); - DelayedTask task = new DelayedTask(command, null, this, d, 0L); - scheduleDelayedTask(task, d); - return task; + return sched(command, (Callable)null, + schedulerDelay(delay, unit), 0L); } public ScheduledFuture schedule(Callable callable, @@ -3747,10 +3767,8 @@ public ScheduledFuture schedule(Callable callable, TimeUnit unit) { if (callable == null || unit == null) throw new NullPointerException(); - long d = nanoDelay(delay, unit); - DelayedTask task = new DelayedTask(null, callable, this, d, 0L); - scheduleDelayedTask(task, d); - return task; + return sched(null, callable, + schedulerDelay(delay, unit), 0L); } public ScheduledFuture scheduleAtFixedRate(Runnable command, @@ -3761,10 +3779,9 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, throw new NullPointerException(); if (period <= 0L) throw new IllegalArgumentException(); - long d = nanoDelay(initialDelay, unit), p = nanoDelay(period, unit); - DelayedTask task = new DelayedTask(command, null, this, d, -p); - scheduleDelayedTask(task, d); - return task; + return sched(command, (Callable)null, + schedulerDelay(initialDelay, unit), + -schedulerDelay(period, unit)); } public ScheduledFuture scheduleWithFixedDelay(Runnable command, @@ -3775,10 +3792,9 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, throw new NullPointerException(); if (delay <= 0L) throw new IllegalArgumentException(); - long d = nanoDelay(initialDelay, unit), p = nanoDelay(delay, unit); - DelayedTask task = new DelayedTask(command, null, this, d, p); - scheduleDelayedTask(task, d); - return task; + return sched(command, (Callable)null, + schedulerDelay(initialDelay, unit), + schedulerDelay(delay, unit)); } /** From 1f0a5cf45defcb63359c08589b117ae617da7082 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Tue, 21 Jan 2025 14:45:30 -0500 Subject: [PATCH 10/40] Use nanoTimeOrigin --- .../java/util/concurrent/ForkJoinPool.java | 66 +++++++++++-------- .../java/util/concurrent/ForkJoinTask.java | 8 +-- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index c71ba7e58f8ea..25dd12853b0f2 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3389,12 +3389,14 @@ static final class DelayScheduler extends Thread { private static final long SCHEDULERSTATE; private static final long ADDITIONS; private static final long REMOVALS; + static final long nanoTimeOrigin; static { U = Unsafe.getUnsafe(); Class klass = DelayScheduler.class; SCHEDULERSTATE = U.objectFieldOffset(klass, "schedulerState"); ADDITIONS = U.objectFieldOffset(klass, "additions"); REMOVALS = U.objectFieldOffset(klass, "removals"); + nanoTimeOrigin = System.nanoTime(); } DelayScheduler(ForkJoinPool p) { @@ -3402,6 +3404,10 @@ static final class DelayScheduler extends Thread { pool = p; } + static final long now() { + return System.nanoTime() - nanoTimeOrigin; + } + private boolean compareAndSetSchedulerState(int c, int v) { return U.compareAndSetInt(this, SCHEDULERSTATE, c, v); } @@ -3447,11 +3453,10 @@ public final void run() { ThreadLocalRandom.localInit(); heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; runScheduler(pool); + cancelAll(); } finally { - ForkJoinPool p; schedulerState = STOPPED; - if ((p = pool) != null) - p.tryTerminate(false, false); + pool.tryTerminate(false, false); } } @@ -3474,27 +3479,29 @@ private void runScheduler(ForkJoinPool p) { int prevState = schedulerState; int hs = processPending(p, heap, heapSize); waitTime = (hs > 0) ? submitReadyTasks(p, heap, hs) : 0L; + boolean working = (waitTime != 0L); int state = schedulerState; - if (waitTime != 0L && (state & WORKING) == 0) + if (working && (state & WORKING) == 0) getAndBitwiseOrSchedulerState(WORKING); else if (state != prevState) ; else if ((state & ACTIVE) != 0) compareAndSetSchedulerState(state, state & ~ACTIVE); - else if (waitTime != 0L || (state & WORKING) == 0 || + else if (working || (state & WORKING) == 0 || compareAndSetSchedulerState(state, state & ~WORKING)) idle = true; } else { idle = false; - Thread.interrupted(); // clear - if ((schedulerState & ACTIVE) == 0) + if (waitTime != 0L) // 1 usec minumum timed wait + waitTime = Math.max(waitTime, 1000L); + if (!Thread.interrupted() && + (schedulerState & ACTIVE) == 0) U.park(false, waitTime); getAndBitwiseOrSchedulerState(ACTIVE | WORKING); } } - cancelAll(); } } @@ -3526,7 +3533,7 @@ private long submitReadyTasks(ForkJoinPool p, DelayedTask[] h, int hs) { if (hs > 0 && p != null && h != null && h.length > 0) { DelayedTask first; int s = hs; - long now = System.nanoTime(); + long now = now(); while ((first = h[0]) != null) { int stat; long d = first.when - now; @@ -3542,7 +3549,7 @@ private long submitReadyTasks(ForkJoinPool p, DelayedTask[] h, int hs) { } first.heapIndex = -1; h[0] = null; - s = (s > 1) ? heapReplace(now, h, 0, s) : 0; + s = (s > 1) ? heapReplace(h, 0, s) : 0; if (stat >= 0) pushReadyTask(first, p); if (s == 0) @@ -3567,7 +3574,7 @@ else if (additions != null) U.getAndSetReference(this, ADDITIONS, null); if (t == null) break; - long now = System.nanoTime(); + long now = now(); do { int idx, cap = h.length, newCap; if ((next = t.nextPending) != null) @@ -3590,7 +3597,7 @@ else if (t.when - now <= 0L) if (s >= cap) // can't grow t.trySetCancelled(); else if (s > 0) - s = heapAdd(now, h, s, t); + s = heapAdd(h, s, t); else if (cap > 0) { h[0] = t; s = 1; @@ -3600,7 +3607,7 @@ else if (cap > 0) { else if (idx < s && s <= cap && h[idx] == t) { h[idx] = null; t.heapIndex = -1; - s = (s > 1) ? heapReplace(now, h, idx, s) : 0; + s = (s > 1) ? heapReplace(h, idx, s) : 0; } } while ((t = next) != null); } @@ -3610,7 +3617,7 @@ else if (idx < s && s <= cap && h[idx] == t) { return s; } - private static int heapAdd(long now, DelayedTask[] h, + private static int heapAdd(DelayedTask[] h, int s, DelayedTask t) { if (h != null && s >= 0 && s < h.length && t != null) { DelayedTask u, p; @@ -3619,9 +3626,9 @@ private static int heapAdd(long now, DelayedTask[] h, h[--s] = null; } int k = s++, parent, ck; - long d = t.when - now; + long d = t.when; while (k > 0 && (p = h[parent = (k - 1) >>> 1]) != null && - (p.status < 0 || d < p.when - now)) { + (p.status < 0 || d < p.when)) { p.heapIndex = k; h[k] = p; k = parent; @@ -3632,8 +3639,7 @@ private static int heapAdd(long now, DelayedTask[] h, return s; } - private static int heapReplace(long now, DelayedTask[] h, - int k, int s) { + private static int heapReplace(DelayedTask[] h, int k, int s) { if (k >= 0 && s >= 0 && h != null && s <= h.length) { DelayedTask t = null; while (s > k) { // find uncancelled replacement @@ -3649,11 +3655,11 @@ private static int heapReplace(long now, DelayedTask[] h, } if (t != null) { int child, right; DelayedTask c, r; - long d = t.when - now, rd; + long d = t.when, rd; while ((child = (k << 1) + 1) < s && (c = h[child]) != null) { - long cd = (c.status < 0) ? Long.MAX_VALUE : c.when - now; + long cd = (c.status < 0) ? Long.MAX_VALUE : c.when; if ((right = child + 1) < s && (r = h[right]) != null && - r.status >= 0 && (rd = r.when - now) < cd) { + r.status >= 0 && (rd = r.when) < cd) { cd = rd; c = r; child = right; @@ -3680,7 +3686,7 @@ private void removePeriodicTasks() { if ((stat = t.status) < 0 || t.nextDelay != 0L) { h[i] = null; t.heapIndex = -1; - s = heapSize = ((s > 1) ? heapReplace(now, h, i, s) : 0); + s = heapSize = ((s > 1) ? heapReplace(h, i, s) : 0); if (stat >= 0) t.trySetCancelled(); } @@ -3739,12 +3745,18 @@ private DelayScheduler startDelayScheduler() { private ScheduledFuture sched(Runnable command, Callable callable, long delay, long nextDelay) { - DelayScheduler ds; DelayedTask t; - if (((ds = delayScheduler) == null && - (ds = startDelayScheduler()) == null) || - (runState & SHUTDOWN) != 0L) + DelayScheduler ds; + long when = DelayScheduler.now() + delay; + DelayedTask t = new DelayedTask<>(command, callable, this, nextDelay, + when); + if ((ds = delayScheduler) == null) + ds = startDelayScheduler(); + if (delay <= 0L) + poolSubmit(true, t); + else if (ds == null || (runState & SHUTDOWN) != 0L) throw new RejectedExecutionException(); - ds.add(t = new DelayedTask<>(command, callable, this, delay, nextDelay)); + else + ds.add(t); return t; } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 15aaa44fcc32d..ccae6d0ba9fde 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1879,9 +1879,9 @@ static final class DelayedTask extends InterruptibleTask int heapIndex; // index if queued on heap DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, - long delay, long nextDelay) { + long nextDelay, long when) { heapIndex = -1; - when = System.nanoTime() + delay; + this.when = when; this.runnable = runnable; this.callable = callable; this.pool = pool; @@ -1896,7 +1896,7 @@ final boolean postExec() { // resubmit if periodic if (!p.isShutdown()) { heapIndex = -1; if (d < 0L) - when = System.nanoTime() - d; + when = ForkJoinPool.DelayScheduler.now() - d; else when += d; ds.add(this); @@ -1925,7 +1925,7 @@ public final boolean cancel(boolean mayInterruptIfRunning) { return stat; } public final long getDelay(TimeUnit unit) { - return unit.convert(when - System.nanoTime(), NANOSECONDS); + return unit.convert(when - ForkJoinPool.DelayScheduler.now(), NANOSECONDS); } public int compareTo(Delayed other) { long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); From 97a2920bc9de51d14a1885f6c3beccc9e0177456 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Wed, 22 Jan 2025 19:20:38 -0500 Subject: [PATCH 11/40] Reduce nanoTime usage; extend tck tests --- .../java/util/concurrent/ForkJoinPool.java | 88 +++++----- .../concurrent/tck/ForkJoinPool20Test.java | 153 ++++-------------- 2 files changed, 75 insertions(+), 166 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 25dd12853b0f2..78fd6217db8c0 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3449,14 +3449,17 @@ final void remove(DelayedTask task) { } public final void run() { - try { - ThreadLocalRandom.localInit(); - heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; - runScheduler(pool); - cancelAll(); - } finally { - schedulerState = STOPPED; - pool.tryTerminate(false, false); + ForkJoinPool p; + if ((p = pool) != null) { + try { + ThreadLocalRandom.localInit(); + heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; + runScheduler(p); + cancelAll(); + } finally { + schedulerState = STOPPED; + p.tryTerminate(false, false); + } } } @@ -3470,8 +3473,7 @@ private void runScheduler(ForkJoinPool p) { removedPeriodic = true; removePeriodicTasks(); } - if (waitTime == 0L && - (schedulerState & (ACTIVE | WORKING)) == 0 && + if ((schedulerState & (ACTIVE | WORKING)) == 0 && (p.tryTerminate(false, false) & STOP) != 0L) break; } @@ -3494,8 +3496,6 @@ else if (working || (state & WORKING) == 0 || } else { idle = false; - if (waitTime != 0L) // 1 usec minumum timed wait - waitTime = Math.max(waitTime, 1000L); if (!Thread.interrupted() && (schedulerState & ACTIVE) == 0) U.park(false, waitTime); @@ -3565,51 +3565,48 @@ private int processPending(ForkJoinPool p, DelayedTask[] h, int hs) { int s = hs; if (p != null && h != null) { for (;;) { - DelayedTask t = null, next; - if (removals != null) - t = (DelayedTask) - U.getAndSetReference(this, REMOVALS, null); - else if (additions != null) - t = (DelayedTask) - U.getAndSetReference(this, ADDITIONS, null); + DelayedTask t = + (removals != null) ? + (DelayedTask)U.getAndSetReference(this, REMOVALS, null): + (additions != null) ? + (DelayedTask)U.getAndSetReference(this, ADDITIONS, null): + null; if (t == null) break; - long now = now(); - do { - int idx, cap = h.length, newCap; + for (;;) { + DelayedTask next; int idx, cap, newCap; if ((next = t.nextPending) != null) t.nextPending = null; if ((idx = t.heapIndex) < 0) { if (t.nextDelay != 0L && p.isShutdown()) t.trySetCancelled(); - else if (t.status < 0) - ; - else if (t.when - now <= 0L) - pushReadyTask(t, p); - else { - if (s >= cap && (newCap = cap << 1) > cap) { + else if (t.status >= 0) { + if (s >= (cap = h.length) && + (newCap = cap << 1) > cap) { try { - h = heap = Arrays.copyOf(heap, newCap); + heap = h = Arrays.copyOf(heap, newCap); cap = h.length; } catch (Error | RuntimeException ex) { } } - if (s >= cap) // can't grow + if (s >= cap || cap <= 0) // can't grow t.trySetCancelled(); else if (s > 0) s = heapAdd(h, s, t); - else if (cap > 0) { + else { h[0] = t; s = 1; } } } - else if (idx < s && s <= cap && h[idx] == t) { + else if (idx < s && idx < h.length && h[idx] == t) { h[idx] = null; t.heapIndex = -1; s = (s > 1) ? heapReplace(h, idx, s) : 0; } - } while ((t = next) != null); + if ((t = next) == null) + break; + } } } if (s != hs) @@ -3620,18 +3617,18 @@ else if (idx < s && s <= cap && h[idx] == t) { private static int heapAdd(DelayedTask[] h, int s, DelayedTask t) { if (h != null && s >= 0 && s < h.length && t != null) { - DelayedTask u, p; + DelayedTask u, par; while (s > 0 && (u = h[s - 1]) != null && u.status < 0) { u.heapIndex = -1; // clear trailing cancelled tasks h[--s] = null; } - int k = s++, parent, ck; + int k = s++, pk; long d = t.when; - while (k > 0 && (p = h[parent = (k - 1) >>> 1]) != null && - (p.status < 0 || d < p.when)) { - p.heapIndex = k; - h[k] = p; - k = parent; + while (k > 0 && (par = h[pk = (k - 1) >>> 1]) != null && + (d < par.when || par.status < 0)) { + par.heapIndex = k; + h[k] = par; + k = pk; } t.heapIndex = k; h[k] = t; @@ -3654,21 +3651,21 @@ private static int heapReplace(DelayedTask[] h, int k, int s) { } } if (t != null) { - int child, right; DelayedTask c, r; + int ck, rk; DelayedTask c, r; long d = t.when, rd; - while ((child = (k << 1) + 1) < s && (c = h[child]) != null) { + while ((ck = (k << 1) + 1) < s && (c = h[ck]) != null) { long cd = (c.status < 0) ? Long.MAX_VALUE : c.when; - if ((right = child + 1) < s && (r = h[right]) != null && + if ((rk = ck + 1) < s && (r = h[rk]) != null && r.status >= 0 && (rd = r.when) < cd) { cd = rd; c = r; - child = right; + ck = rk; } if (d <= cd) break; c.heapIndex = k; h[k] = c; - k = child; + k = ck; } t.heapIndex = k; h[k] = t; @@ -3681,7 +3678,6 @@ private void removePeriodicTasks() { DelayedTask[] h; int s; if ((s = heapSize) > 0 && (h = heap) != null && h.length >= s) { DelayedTask t; int stat; - long now = System.nanoTime(); for (int i = 0; i < s && (t = h[s]) != null; ) { if ((stat = t.status) < 0 || t.nextDelay != 0L) { h[i] = null; diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index 73d7b3c282c08..71a01debc7de0 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -263,6 +263,29 @@ public Boolean realCall() { } } + /** + * delayed schedule of callable successfully executes after delay + * even if shutdown. + */ + public void testSchedule1b() throws Exception { + final ForkJoinPool p = new ForkJoinPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Callable task = new CheckedCallable<>() { + public Boolean realCall() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + return Boolean.TRUE; + }}; + Future f = p.schedule(task, timeoutMillis(), MILLISECONDS); + p.shutdown(); + assertSame(Boolean.TRUE, f.get()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + assertEquals(0L, done.getCount()); + } + } + /** * delayed schedule of runnable successfully executes after delay */ @@ -378,6 +401,7 @@ static class RunnableCounter implements Runnable { * scheduleAtFixedRate executes series of tasks at given rate. * Eventually, it must hold that: * cycles - 1 <= elapsedMillis/delay < cycles + * Additionally, periodic tasks are not run after shutdown. */ public void testFixedRateSequence() throws InterruptedException { final ForkJoinPool p = new ForkJoinPool(4); @@ -392,20 +416,21 @@ public void testFixedRateSequence() throws InterruptedException { p.scheduleAtFixedRate(task, 0, delay, MILLISECONDS); final int totalDelayMillis = (cycles - 1) * delay; await(done, totalDelayMillis + LONG_DELAY_MS); - periodicTask.cancel(true); final long elapsedMillis = millisElapsedSince(startTime); assertTrue(elapsedMillis >= totalDelayMillis); if (elapsedMillis <= cycles * delay) return; - // else retry with longer delay + periodicTask.cancel(true); // retry with longer delay } fail("unexpected execution rate"); } } /** - * scheduleWithFixedDelay executes series of tasks with given period. - * Eventually, it must hold that each task starts at least delay and at - * most 2 * delay after the termination of the previous task. + * scheduleWithFixedDelay executes series of tasks with given + * period. Eventually, it must hold that each task starts at + * least delay and at most 2 * delay after the termination of the + * previous task. Additionally, periodic tasks are not run after + * shutdown. */ public void testFixedDelaySequence() throws InterruptedException { final ForkJoinPool p = new ForkJoinPool(1); @@ -436,12 +461,11 @@ public void realRun() { p.scheduleWithFixedDelay(task, 0, delay, MILLISECONDS); final int totalDelayMillis = (cycles - 1) * delay; await(done, totalDelayMillis + cycles * LONG_DELAY_MS); - periodicTask.cancel(true); final long elapsedMillis = millisElapsedSince(startTime); assertTrue(elapsedMillis >= totalDelayMillis); if (!tryLongerDelay.get()) return; - // else retry with longer delay + periodicTask.cancel(true); // retry with longer delay } fail("unexpected execution rate"); } @@ -497,9 +521,9 @@ public void testSubmittedTasksRejectedWhenShutdown() throws InterruptedException done.countDown(); // release blocking tasks assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - // assertTaskSubmissionsAreRejected(p); } } + /** * A fixed delay task with overflowing period should not prevent a * one-shot task from executing. @@ -520,6 +544,7 @@ public void testScheduleWithFixedDelay_overflow() throws Exception { await(immediateDone); } } + /** * shutdownNow cancels tasks that were not run */ @@ -540,116 +565,4 @@ public void testShutdownNow_delayedTasks() throws InterruptedException { assertTrue(p.isTerminated()); } - - /** - * Periodic tasks are nt run after shutdown and - * delayed tasks keep running after shutdown. - */ - // @SuppressWarnings("FutureReturnValueIgnored") - // public void testShutdown_cancellation() throws Exception { - // final int poolSize = 4; - // final ForkJoinPool p = new ForkJoinPool(poolSize); - // final ThreadLocalRandom rnd = ThreadLocalRandom.current(); - // final long delay = 1; - // final int rounds = 2; - - // // Strategy: Wedge the pool with one wave of "blocker" tasks, - // // then add a second wave that waits in the queue until unblocked. - // final AtomicInteger ran = new AtomicInteger(0); - // final CountDownLatch poolBlocked = new CountDownLatch(poolSize); - // final CountDownLatch unblock = new CountDownLatch(1); - // final RuntimeException exception = new RuntimeException(); - - // class Task implements Runnable { - // public void run() { - // try { - // ran.getAndIncrement(); - // poolBlocked.countDown(); - // await(unblock); - // } catch (Throwable fail) { threadUnexpectedException(fail); } - // } - // } - - // class PeriodicTask extends Task { - // PeriodicTask(int rounds) { this.rounds = rounds; } - // int rounds; - // public void run() { - // if (--rounds == 0) super.run(); - // // throw exception to surely terminate this periodic task, - // // but in a separate execution and in a detectable way. - // if (rounds == -1) throw exception; - // } - // } - - // Runnable task = new Task(); - - // List> immediates = new ArrayList<>(); - // List> delayeds = new ArrayList<>(); - // List> periodics = new ArrayList<>(); - - // immediates.add(p.submit(task)); - // delayeds.add(p.schedule(task, delay, MILLISECONDS)); - // periodics.add(p.scheduleAtFixedRate( - // new PeriodicTask(rounds), delay, 1, MILLISECONDS)); - // periodics.add(p.scheduleWithFixedDelay( - // new PeriodicTask(rounds), delay, 1, MILLISECONDS)); - - // await(poolBlocked); - - // assertEquals(poolSize, ran.get()); - - // // Add second wave of tasks. - // immediates.add(p.submit(task)); - // delayeds.add(p.schedule(task, delay, MILLISECONDS)); - // periodics.add(p.scheduleAtFixedRate( - // new PeriodicTask(rounds), delay, 1, MILLISECONDS)); - // periodics.add(p.scheduleWithFixedDelay( - // new PeriodicTask(rounds), delay, 1, MILLISECONDS)); - - // assertEquals(poolSize, ran.get()); - - // immediates.forEach( - // f -> assertTrue( - // (!(f instanceof ScheduledFuture) || - // ((ScheduledFuture)f).getDelay(NANOSECONDS) <= 0L))); - - // Stream.of(immediates, delayeds, periodics).flatMap(Collection::stream) - // .forEach(f -> assertFalse(f.isDone())); - - // try { p.shutdown(); } catch (SecurityException ok) { return; } - // assertTrue(p.isShutdown()); - // assertFalse(p.isTerminated()); - - // assertThrows( - // RejectedExecutionException.class, - // () -> p.submit(task), - // () -> p.schedule(task, 1, SECONDS), - // () -> p.scheduleAtFixedRate( - // new PeriodicTask(1), 1, 1, SECONDS), - // () -> p.scheduleWithFixedDelay( - // new PeriodicTask(2), 1, 1, SECONDS)); - - // immediates.forEach(f -> assertFalse(f.isDone())); - - // assertFalse(delayeds.get(0).isDone()); - // assertFalse(delayeds.get(1).isDone()); - // periodics.subList(0, 2).forEach(f -> assertFalse(f.isDone())); - // periodics.subList(2, 4).forEach(f -> assertTrue(f.isCancelled())); - - // unblock.countDown(); // Release all pool threads - - // assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - // assertTrue(p.isTerminated()); - - // Stream.of(immediates, delayeds, periodics).flatMap(Collection::stream) - // .forEach(f -> assertTrue(f.isDone())); - - // for (Future f : immediates) assertNull(f.get()); - - // assertNull(delayeds.get(0).get()); - // assertNull(delayeds.get(1).get()); - // periodics.forEach(f -> assertTrue(f.isCancelled())); - - // } - } From f9aa135ea3baa74e11f1b6d8dd91f19122fa6919 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Thu, 23 Jan 2025 10:30:56 -0500 Subject: [PATCH 12/40] Simplify scheduler state tracking --- .../java/util/concurrent/ForkJoinPool.java | 204 +++++++++--------- 1 file changed, 96 insertions(+), 108 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 78fd6217db8c0..1768ad75b6c6e 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3372,28 +3372,24 @@ public T invokeAny(Collection> tasks, static final class DelayScheduler extends Thread { private static final int INITIAL_HEAP_CAPACITY = 1 << 6; - private static final int ACTIVE = 1 << 0; - private static final int WORKING = 1 << 1; // set when not in untimed park - private static final int STOPPED = 1 << 2; private final ForkJoinPool pool; private DelayedTask[] heap; + private int heapSize; @jdk.internal.vm.annotation.Contended() - private volatile int schedulerState; + private volatile int active; // 0: inactive, <0: stopped, >0: running @jdk.internal.vm.annotation.Contended() private volatile DelayedTask additions; @jdk.internal.vm.annotation.Contended() private volatile DelayedTask removals; - @jdk.internal.vm.annotation.Contended() - private int heapSize; private static final Unsafe U; - private static final long SCHEDULERSTATE; + private static final long ACTIVE; private static final long ADDITIONS; private static final long REMOVALS; static final long nanoTimeOrigin; static { U = Unsafe.getUnsafe(); Class klass = DelayScheduler.class; - SCHEDULERSTATE = U.objectFieldOffset(klass, "schedulerState"); + ACTIVE = U.objectFieldOffset(klass, "active"); ADDITIONS = U.objectFieldOffset(klass, "additions"); REMOVALS = U.objectFieldOffset(klass, "removals"); nanoTimeOrigin = System.nanoTime(); @@ -3408,58 +3404,56 @@ static final long now() { return System.nanoTime() - nanoTimeOrigin; } - private boolean compareAndSetSchedulerState(int c, int v) { - return U.compareAndSetInt(this, SCHEDULERSTATE, c, v); - } - - private int getAndBitwiseOrSchedulerState(int v) { - return U.getAndBitwiseOrInt(this, SCHEDULERSTATE, v); - } - final boolean activate() { int state; - if (((state = schedulerState) & STOPPED) != 0) + if ((state = active) < 0) // stopped return true; - if ((state & ACTIVE) == 0 && - (getAndBitwiseOrSchedulerState(ACTIVE) & ACTIVE) == 0) + if (state == 0 && U.getAndBitwiseOrInt(this, ACTIVE, 1) == 0) U.unpark(this); return false; } final boolean canShutDown() { - return (schedulerState & (ACTIVE | WORKING)) == 0; + int state; DelayedTask[] h; + return ((state = active) < 0 || + (state == 0 && heapSize <= 0 && + ((h = heap) == null || h.length <= 0 || h[0] == null) && + active <= 0)); } final void add(DelayedTask task) { + DelayedTask f = additions; if (task != null) { - do {} while (!U.compareAndSetReference( - this, ADDITIONS, - task.nextPending = additions, task)); + do {} while ( + f != (f = (DelayedTask) + U.compareAndExchangeReference( + this, ADDITIONS, task.nextPending = f, task))); activate(); } } final void remove(DelayedTask task) { + DelayedTask f = removals; if (task != null) { - do {} while (!U.compareAndSetReference( - this, REMOVALS, - task.nextPending = removals, task)); + do {} while ( + f != (f = (DelayedTask) + U.compareAndExchangeReference( + this, REMOVALS, task.nextPending = f, task))); activate(); } } public final void run() { - ForkJoinPool p; - if ((p = pool) != null) { - try { - ThreadLocalRandom.localInit(); - heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; - runScheduler(p); - cancelAll(); - } finally { - schedulerState = STOPPED; + ForkJoinPool p = pool; + try { + ThreadLocalRandom.localInit(); + heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; + runScheduler(p); + cancelAll(); + } finally { + active = 1 << 31; // set negative + if (p != null) p.tryTerminate(false, false); - } } } @@ -3473,33 +3467,27 @@ private void runScheduler(ForkJoinPool p) { removedPeriodic = true; removePeriodicTasks(); } - if ((schedulerState & (ACTIVE | WORKING)) == 0 && + if (canShutDown() && (p.tryTerminate(false, false) & STOP) != 0L) break; } if (!idle) { - int prevState = schedulerState; + int state = active; int hs = processPending(p, heap, heapSize); waitTime = (hs > 0) ? submitReadyTasks(p, heap, hs) : 0L; - boolean working = (waitTime != 0L); - int state = schedulerState; - if (working && (state & WORKING) == 0) - getAndBitwiseOrSchedulerState(WORKING); - else if (state != prevState) - ; - else if ((state & ACTIVE) != 0) - compareAndSetSchedulerState(state, state & ~ACTIVE); - else if (working || (state & WORKING) == 0 || - compareAndSetSchedulerState(state, - state & ~WORKING)) - idle = true; + if (active == state) { + if (state != 0) + U.compareAndSetInt(this, ACTIVE, 1, 0); + else + idle = true; + } } else { idle = false; - if (!Thread.interrupted() && - (schedulerState & ACTIVE) == 0) + Thread.interrupted(); // clear + if (active == 0) U.park(false, waitTime); - getAndBitwiseOrSchedulerState(ACTIVE | WORKING); + active = 1; } } } @@ -3548,11 +3536,9 @@ private long submitReadyTasks(ForkJoinPool p, DelayedTask[] h, int hs) { break; } first.heapIndex = -1; - h[0] = null; - s = (s > 1) ? heapReplace(h, 0, s) : 0; if (stat >= 0) pushReadyTask(first, p); - if (s == 0) + if ((s = heapReplace(h, 0, s)) == 0) break; } if (s != hs) @@ -3577,32 +3563,26 @@ private int processPending(ForkJoinPool p, DelayedTask[] h, int hs) { DelayedTask next; int idx, cap, newCap; if ((next = t.nextPending) != null) t.nextPending = null; - if ((idx = t.heapIndex) < 0) { - if (t.nextDelay != 0L && p.isShutdown()) - t.trySetCancelled(); - else if (t.status >= 0) { - if (s >= (cap = h.length) && - (newCap = cap << 1) > cap) { - try { - heap = h = Arrays.copyOf(heap, newCap); - cap = h.length; - } catch (Error | RuntimeException ex) { - } - } - if (s >= cap || cap <= 0) // can't grow - t.trySetCancelled(); - else if (s > 0) - s = heapAdd(h, s, t); - else { - h[0] = t; - s = 1; + if ((idx = t.heapIndex) >= 0) { + t.heapIndex = -1; + if (idx < s && idx < h.length && h[idx] == t) + s = heapReplace(h, idx, s); + } + else if (t.nextDelay != 0L && p.isShutdown()) + t.trySetCancelled(); + else if (t.status >= 0) { + if (s >= (cap = h.length) && + (newCap = cap << 1) > cap) { + try { + heap = h = Arrays.copyOf(heap, newCap); + cap = h.length; + } catch (Error | RuntimeException ex) { } } - } - else if (idx < s && idx < h.length && h[idx] == t) { - h[idx] = null; - t.heapIndex = -1; - s = (s > 1) ? heapReplace(h, idx, s) : 0; + if (s >= cap || cap <= 0) // can't grow + t.trySetCancelled(); + else + s = heapAdd(h, s, t); } if ((t = next) == null) break; @@ -3637,38 +3617,46 @@ private static int heapAdd(DelayedTask[] h, } private static int heapReplace(DelayedTask[] h, int k, int s) { - if (k >= 0 && s >= 0 && h != null && s <= h.length) { - DelayedTask t = null; - while (s > k) { // find uncancelled replacement - DelayedTask u = h[--s]; - h[s] = null; - if (u != null) { - if (u.status >= 0) { - t = u; - break; + if (k >= 0 && s > 0 && h != null && s <= h.length) { + int last = s - 1; + h[k] = null; + if (k == last) + s = last; + else { + DelayedTask t = null; + while (s > k) { // find uncancelled replacement + DelayedTask u = h[last]; + h[last] = null; + s = last; + last = s - 1; + if (u != null) { + if (u.status >= 0) { + t = u; + break; + } + u.heapIndex = -1; } - u.heapIndex = -1; } - } - if (t != null) { - int ck, rk; DelayedTask c, r; - long d = t.when, rd; - while ((ck = (k << 1) + 1) < s && (c = h[ck]) != null) { - long cd = (c.status < 0) ? Long.MAX_VALUE : c.when; - if ((rk = ck + 1) < s && (r = h[rk]) != null && - r.status >= 0 && (rd = r.when) < cd) { - cd = rd; - c = r; - ck = rk; + if (t != null) { + int ck, rk; DelayedTask c, r; + long d = t.when, rd; + while ((ck = (k << 1) + 1) < s && (c = h[ck]) != null) { + long cd = (c.status < 0) ? Long.MAX_VALUE : c.when; + if ((rk = ck + 1) < s && (r = h[rk]) != null && + r.status >= 0 && (rd = r.when) < cd) { + cd = rd; + c = r; + ck = rk; + } + if (d <= cd) + break; + c.heapIndex = k; + h[k] = c; + k = ck; } - if (d <= cd) - break; - c.heapIndex = k; - h[k] = c; - k = ck; + t.heapIndex = k; + h[k] = t; } - t.heapIndex = k; - h[k] = t; } } return s; From 798fe64ecf31dc65132ee2ed1080dbaf2df491f5 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Fri, 24 Jan 2025 12:17:22 -0500 Subject: [PATCH 13/40] Refactor delay scheduler pool submissions --- .../java/util/concurrent/ForkJoinPool.java | 111 +++++++++--------- .../java/util/concurrent/ForkJoinTask.java | 2 +- 2 files changed, 54 insertions(+), 59 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 1768ad75b6c6e..8c7c379a82ee6 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -1049,6 +1049,7 @@ public class ForkJoinPool extends AbstractExecutorService static final int DROPPED = 1 << 16; // removed from ctl counts static final int UNCOMPENSATE = 1 << 16; // tryCompensate return static final int IDLE = 1 << 16; // phase seqlock/version count + static final int MIN_QUEUES_SIZE = 4; // ensure > 1 external slot /* * Bits and masks for ctl and bounds are packed with 4 16 bit subfields: @@ -2521,7 +2522,7 @@ final ForkJoinTask nextTaskFor(WorkQueue w) { * Finds and locks a WorkQueue for an external submitter, or * throws RejectedExecutionException if shutdown or terminating. * @param r current ThreadLocalRandom.getProbe() value - * @param isSubmit false if this is for a common pool fork + * @param rejectOnShutdown true if throw RJE when shutdown */ private WorkQueue submissionQueue(int r, boolean rejectOnShutdown) { if (r == 0) { @@ -2580,12 +2581,12 @@ private void poolSubmit(boolean signalIfEmpty, ForkJoinTask task) { * Returns queue for an external submission, bypassing call to * submissionQueue if already established and unlocked. */ - final WorkQueue externalSubmissionQueue() { + final WorkQueue externalSubmissionQueue(boolean rejectOnShutdown) { WorkQueue[] qs; WorkQueue q; int n; int r = ThreadLocalRandom.getProbe(); return (((qs = queues) != null && (n = qs.length) > 0 && (q = qs[r & EXTERNAL_ID_MASK & (n - 1)]) != null && r != 0 && - q.tryLockPhase()) ? q : submissionQueue(r, true)); + q.tryLockPhase()) ? q : submissionQueue(r, rejectOnShutdown)); } /** @@ -2974,7 +2975,8 @@ public ForkJoinPool(int parallelism, throw new IllegalArgumentException(); if (factory == null || unit == null) throw new NullPointerException(); - int size = 1 << (33 - Integer.numberOfLeadingZeros(p - 1)); + int size = Math.max(MIN_QUEUES_SIZE, + 1 << (33 - Integer.numberOfLeadingZeros(p - 1))); this.parallelism = p; this.factory = factory; this.ueh = handler; @@ -3032,7 +3034,9 @@ private ForkJoinPool(byte forCommonPoolOnly) { if (preset == 0) pc = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); int p = Math.min(pc, MAX_CAP); - int size = (p == 0) ? 1 : 1 << (33 - Integer.numberOfLeadingZeros(p-1)); + int size = Math.max(MIN_QUEUES_SIZE, + (p == 0) ? 1 : + 1 << (33 - Integer.numberOfLeadingZeros(p-1))); this.parallelism = p; this.config = ((preset & LMASK) | (((long)maxSpares) << TC_SHIFT) | (1L << RC_SHIFT)); @@ -3209,7 +3213,7 @@ public ForkJoinTask submit(Runnable task) { */ public ForkJoinTask externalSubmit(ForkJoinTask task) { Objects.requireNonNull(task); - externalSubmissionQueue().push(task, this, false); + externalSubmissionQueue(true).push(task, this, false); return task; } @@ -3444,16 +3448,21 @@ final void remove(DelayedTask task) { } public final void run() { - ForkJoinPool p = pool; - try { - ThreadLocalRandom.localInit(); - heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; - runScheduler(p); - cancelAll(); - } finally { - active = 1 << 31; // set negative - if (p != null) + ForkJoinPool p; + ThreadLocalRandom.localInit(); + if ((p = pool) != null) { + try { + WorkQueue q; // establish default submission queue + heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; + if ((q = p.externalSubmissionQueue(false)) != null) { + q.unlockPhase(); + runScheduler(p); + cancelAll(); + } + } finally { + active = 1 << 31; // set negative p.tryTerminate(false, false); + } } } @@ -3467,7 +3476,7 @@ private void runScheduler(ForkJoinPool p) { removedPeriodic = true; removePeriodicTasks(); } - if (canShutDown() && + if (idle && canShutDown() && (p.tryTerminate(false, false) & STOP) != 0L) break; } @@ -3493,51 +3502,36 @@ private void runScheduler(ForkJoinPool p) { } } - private void pushReadyTask(DelayedTask task, ForkJoinPool p) { - if (task != null && p != null) { - WorkQueue[] qs; WorkQueue q; int n; - boolean cancel = false; // bypass poolSubmit runState checks - int r = ThreadLocalRandom.getProbe(); - if ((p.runState & STOP) != 0L || - (((qs = p.queues) == null || (n = qs.length) <= 0 || - (q = qs[r & EXTERNAL_ID_MASK & (n - 1)]) == null || - r == 0 || !q.tryLockPhase()) && - (q = p.submissionQueue(r, false)) == null)) - cancel = true; - else { - try { - q.push(task, p, false); - } catch(Error | RuntimeException ex) { - cancel = true; - } - } - if (cancel) - task.trySetCancelled(); - } - } - private long submitReadyTasks(ForkJoinPool p, DelayedTask[] h, int hs) { - long waitTime = 0L; - if (hs > 0 && p != null && h != null && h.length > 0) { + long waitTime = 0L, prs; + if (hs > 0 && h != null && h.length > 0 && p != null && + ((prs = p.runState) & STOP) == 0L) { DelayedTask first; int s = hs; long now = now(); while ((first = h[0]) != null) { - int stat; + boolean cancel = false; long d = first.when - now; - if (first.nextDelay != 0L && p.isShutdown()) { - first.trySetCancelled(); - stat = -1; - } - else - stat = first.status; - if (stat >= 0 && d > 0L) { + if (first.nextDelay != 0L && (prs & SHUTDOWN) != 0L) + cancel = true; + else if (d > 0L) { waitTime = d; break; } first.heapIndex = -1; - if (stat >= 0) - pushReadyTask(first, p); + try { + WorkQueue q; + if ((q = p.externalSubmissionQueue(false)) == null) + cancel = true; // terminating + else if (first.status < 0 || cancel) + q.unlockPhase(); + else + q.push(first, p, false); + } catch(Error | RuntimeException ex) { + cancel = true; + } + if (cancel) + first.trySetCancelled(); if ((s = heapReplace(h, 0, s)) == 0) break; } @@ -3561,6 +3555,7 @@ private int processPending(ForkJoinPool p, DelayedTask[] h, int hs) { break; for (;;) { DelayedTask next; int idx, cap, newCap; + boolean cancel = false; if ((next = t.nextPending) != null) t.nextPending = null; if ((idx = t.heapIndex) >= 0) { @@ -3569,7 +3564,7 @@ private int processPending(ForkJoinPool p, DelayedTask[] h, int hs) { s = heapReplace(h, idx, s); } else if (t.nextDelay != 0L && p.isShutdown()) - t.trySetCancelled(); + cancel = true; else if (t.status >= 0) { if (s >= (cap = h.length) && (newCap = cap << 1) > cap) { @@ -3580,10 +3575,12 @@ else if (t.status >= 0) { } } if (s >= cap || cap <= 0) // can't grow - t.trySetCancelled(); + cancel = true; else s = heapAdd(h, s, t); } + if (cancel) + t.trySetCancelled(); if ((t = next) == null) break; } @@ -3596,7 +3593,7 @@ else if (t.status >= 0) { private static int heapAdd(DelayedTask[] h, int s, DelayedTask t) { - if (h != null && s >= 0 && s < h.length && t != null) { + if (s >= 0 && h != null && s < h.length && t != null) { DelayedTask u, par; while (s > 0 && (u = h[s - 1]) != null && u.status < 0) { u.heapIndex = -1; // clear trailing cancelled tasks @@ -3627,8 +3624,7 @@ private static int heapReplace(DelayedTask[] h, int k, int s) { while (s > k) { // find uncancelled replacement DelayedTask u = h[last]; h[last] = null; - s = last; - last = s - 1; + s = last--; if (u != null) { if (u.status >= 0) { t = u; @@ -3668,11 +3664,10 @@ private void removePeriodicTasks() { DelayedTask t; int stat; for (int i = 0; i < s && (t = h[s]) != null; ) { if ((stat = t.status) < 0 || t.nextDelay != 0L) { - h[i] = null; t.heapIndex = -1; - s = heapSize = ((s > 1) ? heapReplace(h, i, s) : 0); if (stat >= 0) t.trySetCancelled(); + s = heapReplace(h, i, s); } } } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index ccae6d0ba9fde..c371f3632c357 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -643,7 +643,7 @@ public final ForkJoinTask fork() { p = wt.pool; } else - q = (p = ForkJoinPool.common).externalSubmissionQueue(); + q = (p = ForkJoinPool.common).externalSubmissionQueue(false); q.push(this, p, internal); return this; } From d083e91696afe67f5fc277d699412271b6fa04c6 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sat, 25 Jan 2025 17:02:19 -0500 Subject: [PATCH 14/40] improve removal cost balance --- .../java/util/concurrent/ForkJoinPool.java | 239 +++++++++--------- 1 file changed, 117 insertions(+), 122 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 8c7c379a82ee6..925dcc3f8d120 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3468,19 +3468,24 @@ public final void run() { private void runScheduler(ForkJoinPool p) { if (p != null) { - boolean idle = false, removedPeriodic = false; - long waitTime = 0L, prs; - while (((prs = p.runState) & STOP) == 0L) { - if ((prs & SHUTDOWN) != 0L) { - if (!removedPeriodic) { - removedPeriodic = true; - removePeriodicTasks(); - } - if (idle && canShutDown() && - (p.tryTerminate(false, false) & STOP) != 0L) - break; + boolean idle = false; + int canStop = 0; + long waitTime = 0L; + for (;;) { + long prs; + if (((prs = p.runState) & STOP) != 0L) + break; + else if ((prs & SHUTDOWN) != 0L && + (canStop = tryStopOnShutdown(p, canStop)) < 0) + break; + else if (idle) { + Thread.interrupted(); // clear + if (active == 0) + U.park(false, waitTime); + active = 1; + idle = false; } - if (!idle) { + else { int state = active; int hs = processPending(p, heap, heapSize); waitTime = (hs > 0) ? submitReadyTasks(p, heap, hs) : 0L; @@ -3491,44 +3496,40 @@ private void runScheduler(ForkJoinPool p) { idle = true; } } - else { - idle = false; - Thread.interrupted(); // clear - if (active == 0) - U.park(false, waitTime); - active = 1; - } } } } private long submitReadyTasks(ForkJoinPool p, DelayedTask[] h, int hs) { - long waitTime = 0L, prs; - if (hs > 0 && h != null && h.length > 0 && p != null && + long waitTime = 0L, prs; int s; + if ((s = hs) > 0 && h != null && h.length > 0 && p != null && ((prs = p.runState) & STOP) == 0L) { - DelayedTask first; - int s = hs; - long now = now(); - while ((first = h[0]) != null) { - boolean cancel = false; - long d = first.when - now; - if (first.nextDelay != 0L && (prs & SHUTDOWN) != 0L) - cancel = true; - else if (d > 0L) { - waitTime = d; + for (long now = now();;) { + DelayedTask first; int stat; long d; + if ((first = h[0]) == null) break; + boolean cancel = false; + if ((stat = first.status) >= 0) { + if (first.nextDelay != 0L && (prs & SHUTDOWN) != 0L) + cancel = true; + else if ((d = first.when - now) > 0L) { + waitTime = d; + break; + } } first.heapIndex = -1; - try { - WorkQueue q; - if ((q = p.externalSubmissionQueue(false)) == null) - cancel = true; // terminating - else if (first.status < 0 || cancel) - q.unlockPhase(); - else - q.push(first, p, false); - } catch(Error | RuntimeException ex) { - cancel = true; + if (stat >= 0 && !cancel) { + try { + WorkQueue q; + if ((q = p.externalSubmissionQueue(false)) == null) + cancel = true; // terminating + else if (first.status < 0) + q.unlockPhase(); + else + q.push(first, p, false); + } catch(Error | RuntimeException ex) { + cancel = true; + } } if (cancel) first.trySetCancelled(); @@ -3554,33 +3555,49 @@ private int processPending(ForkJoinPool p, DelayedTask[] h, int hs) { if (t == null) break; for (;;) { - DelayedTask next; int idx, cap, newCap; - boolean cancel = false; + int cap = h.length, idx; DelayedTask next; if ((next = t.nextPending) != null) t.nextPending = null; if ((idx = t.heapIndex) >= 0) { t.heapIndex = -1; - if (idx < s && idx < h.length && h[idx] == t) + if (idx < s && idx < cap && h[idx] == t) s = heapReplace(h, idx, s); } - else if (t.nextDelay != 0L && p.isShutdown()) - cancel = true; + else if (s >= cap || s < 0 || // couldn't resize + (t.nextDelay != 0L && p.isShutdown())) + t.trySetCancelled(); else if (t.status >= 0) { - if (s >= (cap = h.length) && - (newCap = cap << 1) > cap) { - try { - heap = h = Arrays.copyOf(heap, newCap); - cap = h.length; + DelayedTask u; int k, newCap; + while (s > 0 && // clear trailing cancelled tasks + (u = h[s - 1]) != null && u.status < 0) { + u.heapIndex = -1; + h[--s] = null; + } + if ((k = s++) > 0) { // sift up + for (long d = t.when;;) { + int pk; DelayedTask par; + if ((par = h[pk = (k - 1) >>> 1]) == null) + break; + if (par.when <= d && par.status >= 0) + break; + par.heapIndex = k; + h[k] = par; + if ((k = pk) == 0) + break; + } + } + t.heapIndex = k; + h[k] = t; + if (s >= cap && (newCap = cap << 1) > cap) { + DelayedTask[] a = null; + try { // try to resize + a = Arrays.copyOf(heap, newCap); } catch (Error | RuntimeException ex) { } + if (a != null) + cap = (heap = h = a).length; } - if (s >= cap || cap <= 0) // can't grow - cancel = true; - else - s = heapAdd(h, s, t); } - if (cancel) - t.trySetCancelled(); if ((t = next) == null) break; } @@ -3591,86 +3608,64 @@ else if (t.status >= 0) { return s; } - private static int heapAdd(DelayedTask[] h, - int s, DelayedTask t) { - if (s >= 0 && h != null && s < h.length && t != null) { - DelayedTask u, par; - while (s > 0 && (u = h[s - 1]) != null && u.status < 0) { - u.heapIndex = -1; // clear trailing cancelled tasks - h[--s] = null; - } - int k = s++, pk; - long d = t.when; - while (k > 0 && (par = h[pk = (k - 1) >>> 1]) != null && - (d < par.when || par.status < 0)) { - par.heapIndex = k; - h[k] = par; - k = pk; - } - t.heapIndex = k; - h[k] = t; - } - return s; - } - private static int heapReplace(DelayedTask[] h, int k, int s) { if (k >= 0 && s > 0 && h != null && s <= h.length) { - int last = s - 1; - h[k] = null; - if (k == last) - s = last; - else { - DelayedTask t = null; - while (s > k) { // find uncancelled replacement - DelayedTask u = h[last]; - h[last] = null; - s = last--; - if (u != null) { - if (u.status >= 0) { - t = u; - break; - } - u.heapIndex = -1; + DelayedTask t = null, u; + while (--s > k) { // find uncancelled replacement + if ((u = h[s]) != null) { + h[s] = null; + if (u.status >= 0) { + t = u; + break; } + u.heapIndex = -1; } - if (t != null) { - int ck, rk; DelayedTask c, r; - long d = t.when, rd; - while ((ck = (k << 1) + 1) < s && (c = h[ck]) != null) { - long cd = (c.status < 0) ? Long.MAX_VALUE : c.when; - if ((rk = ck + 1) < s && (r = h[rk]) != null && - r.status >= 0 && (rd = r.when) < cd) { - cd = rd; - c = r; - ck = rk; - } - if (d <= cd) - break; - c.heapIndex = k; - h[k] = c; - k = ck; + } + if (t != null) { + long d = t.when, rd; int ck, rk; DelayedTask c, r; + while ((ck = (k << 1) + 1) <= s && (c = h[ck]) != null) { + long cw = c.when; + long cd = (c.status < 0) ? Long.MAX_VALUE : cw; + if ((rk = ck + 1) <= s && (r = h[rk]) != null && + (rd = r.when) < cd && r.status >= 0) { + cd = rd; + c = r; + ck = rk; } - t.heapIndex = k; - h[k] = t; + if (d <= cd) + break; + c.heapIndex = k; + h[k] = c; + k = ck; } + t.heapIndex = k; } + h[k] = t; } return s; } - private void removePeriodicTasks() { + private int tryStopOnShutdown(ForkJoinPool p, int canStop) { DelayedTask[] h; int s; - if ((s = heapSize) > 0 && (h = heap) != null && h.length >= s) { - DelayedTask t; int stat; - for (int i = 0; i < s && (t = h[s]) != null; ) { - if ((stat = t.status) < 0 || t.nextDelay != 0L) { - t.heapIndex = -1; - if (stat >= 0) - t.trySetCancelled(); - s = heapReplace(h, i, s); + if (p != null) { + if ((s = heapSize) > 0 && canStop == 0 && (h = heap) != null && + h.length >= s) { + DelayedTask t; int stat; + for (int i = 0; i < s && (t = h[s]) != null; ) { + if ((stat = t.status) < 0 || t.nextDelay != 0L) { + t.heapIndex = -1; + if (stat >= 0) + t.trySetCancelled(); + s = heapReplace(h, i, s); + } } + heapSize = s; } + if (s == 0 && active <= 0 && + (p.tryTerminate(false, false) & STOP) != 0L) + return -1; } + return 1; } private void cancelAll() { From 1a7f77c8877b8959f17ffc52f0a18a7eb7c1261d Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 26 Jan 2025 11:15:27 -0500 Subject: [PATCH 15/40] Ensure negative nanotime offset --- .../java/util/concurrent/ForkJoinPool.java | 78 +++++++++---------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 925dcc3f8d120..df6cf5471135f 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3389,23 +3389,25 @@ static final class DelayScheduler extends Thread { private static final long ACTIVE; private static final long ADDITIONS; private static final long REMOVALS; - static final long nanoTimeOrigin; + static final long nanoTimeOffset; static { U = Unsafe.getUnsafe(); Class klass = DelayScheduler.class; ACTIVE = U.objectFieldOffset(klass, "active"); ADDITIONS = U.objectFieldOffset(klass, "additions"); REMOVALS = U.objectFieldOffset(klass, "removals"); - nanoTimeOrigin = System.nanoTime(); + long ns = System.nanoTime(); // ensure negative to avoid overflow + nanoTimeOffset = Long.MIN_VALUE + (ns < 0L ? ns : 0L); } - DelayScheduler(ForkJoinPool p) { + DelayScheduler(ForkJoinPool p, String name) { + super(name); setDaemon(true); pool = p; } static final long now() { - return System.nanoTime() - nanoTimeOrigin; + return nanoTimeOffset + System.nanoTime(); } final boolean activate() { @@ -3479,11 +3481,12 @@ else if ((prs & SHUTDOWN) != 0L && (canStop = tryStopOnShutdown(p, canStop)) < 0) break; else if (idle) { + idle = false; Thread.interrupted(); // clear - if (active == 0) + if (active == 0) { U.park(false, waitTime); - active = 1; - idle = false; + active = 1; + } } else { int state = active; @@ -3506,9 +3509,9 @@ private long submitReadyTasks(ForkJoinPool p, DelayedTask[] h, int hs) { ((prs = p.runState) & STOP) == 0L) { for (long now = now();;) { DelayedTask first; int stat; long d; + boolean cancel = false; if ((first = h[0]) == null) break; - boolean cancel = false; if ((stat = first.status) >= 0) { if (first.nextDelay != 0L && (prs & SHUTDOWN) != 0L) cancel = true; @@ -3623,10 +3626,10 @@ private static int heapReplace(DelayedTask[] h, int k, int s) { } if (t != null) { long d = t.when, rd; int ck, rk; DelayedTask c, r; - while ((ck = (k << 1) + 1) <= s && (c = h[ck]) != null) { + while ((ck = (k << 1) + 1) < s && (c = h[ck]) != null) { long cw = c.when; long cd = (c.status < 0) ? Long.MAX_VALUE : cw; - if ((rk = ck + 1) <= s && (r = h[rk]) != null && + if ((rk = ck + 1) < s && (r = h[rk]) != null && (rd = r.when) < cd && r.status >= 0) { cd = rd; c = r; @@ -3647,25 +3650,21 @@ private static int heapReplace(DelayedTask[] h, int k, int s) { private int tryStopOnShutdown(ForkJoinPool p, int canStop) { DelayedTask[] h; int s; - if (p != null) { - if ((s = heapSize) > 0 && canStop == 0 && (h = heap) != null && - h.length >= s) { - DelayedTask t; int stat; - for (int i = 0; i < s && (t = h[s]) != null; ) { - if ((stat = t.status) < 0 || t.nextDelay != 0L) { - t.heapIndex = -1; - if (stat >= 0) - t.trySetCancelled(); - s = heapReplace(h, i, s); - } + if ((s = heapSize) > 0 && canStop == 0 && (h = heap) != null && + h.length >= s) { // remove periodic tasks + DelayedTask t; int stat; + for (int i = 0; i < s && (t = h[s]) != null; ) { + if ((stat = t.status) < 0 || t.nextDelay != 0L) { + t.heapIndex = -1; + if (stat >= 0) + t.trySetCancelled(); + s = heapReplace(h, i, s); } - heapSize = s; } - if (s == 0 && active <= 0 && - (p.tryTerminate(false, false) & STOP) != 0L) - return -1; + heapSize = s; } - return 1; + return (s == 0 && active <= 0 && p != null && + (p.tryTerminate(false, false) & STOP) != 0L) ? -1 : 1; } private void cancelAll() { @@ -3696,10 +3695,11 @@ private DelayScheduler startDelayScheduler() { DelayScheduler ds; if ((ds = delayScheduler) == null) { boolean start = false; + String name = poolName + "-delayScheduler"; long rs = lockRunState(); try { if ((rs & SHUTDOWN) == 0 && (ds = delayScheduler) == null) { - ds = delayScheduler = new DelayScheduler(this); + ds = delayScheduler = new DelayScheduler(this, name); start = true; } } finally { @@ -3707,7 +3707,6 @@ private DelayScheduler startDelayScheduler() { } if (start) { // start outside of lock SharedThreadContainer ctr; - ds.setName(poolName + "-delayScheduler"); if ((ctr = container) != null) ctr.start(ds); else @@ -3721,11 +3720,11 @@ private ScheduledFuture sched(Runnable command, Callable callable, long delay, long nextDelay) { DelayScheduler ds; long when = DelayScheduler.now() + delay; - DelayedTask t = new DelayedTask<>(command, callable, this, nextDelay, - when); + DelayedTask t = + new DelayedTask<>(command, callable, this, nextDelay, when); if ((ds = delayScheduler) == null) ds = startDelayScheduler(); - if (delay <= 0L) + if (delay == 0L) poolSubmit(true, t); else if (ds == null || (runState & SHUTDOWN) != 0L) throw new RejectedExecutionException(); @@ -3734,18 +3733,13 @@ else if (ds == null || (runState & SHUTDOWN) != 0L) return t; } - /** Convert and truncate to maximum supported delay value */ - private static long schedulerDelay(long d, TimeUnit unit) { - return Math.min(unit.toNanos(d), (Long.MAX_VALUE >>> 1) - 1); - } - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); return sched(command, (Callable)null, - schedulerDelay(delay, unit), 0L); + (delay <= 0L) ? 0L : unit.toNanos(delay), 0L); } public ScheduledFuture schedule(Callable callable, @@ -3754,7 +3748,7 @@ public ScheduledFuture schedule(Callable callable, if (callable == null || unit == null) throw new NullPointerException(); return sched(null, callable, - schedulerDelay(delay, unit), 0L); + (delay <= 0L) ? 0L : unit.toNanos(delay), 0L); } public ScheduledFuture scheduleAtFixedRate(Runnable command, @@ -3766,8 +3760,8 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, if (period <= 0L) throw new IllegalArgumentException(); return sched(command, (Callable)null, - schedulerDelay(initialDelay, unit), - -schedulerDelay(period, unit)); + (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay), + -unit.toNanos(period)); } public ScheduledFuture scheduleWithFixedDelay(Runnable command, @@ -3779,8 +3773,8 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, if (delay <= 0L) throw new IllegalArgumentException(); return sched(command, (Callable)null, - schedulerDelay(initialDelay, unit), - schedulerDelay(delay, unit)); + (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay), + unit.toNanos(delay)); } /** From 3da4fd7ee82bb398f56265ea555a8ec104c5c6c9 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 2 Feb 2025 10:07:03 -0500 Subject: [PATCH 16/40] Solidify design; add documentation --- .../java/util/concurrent/ForkJoinPool.java | 590 ++++++++++-------- .../java/util/concurrent/ForkJoinTask.java | 86 ++- 2 files changed, 398 insertions(+), 278 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index df6cf5471135f..9e8898e890229 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -136,6 +136,17 @@ * * * + *

Additionally, this class supports {@link + * ScheduledExecutorService} methods to delay or periodically execute + * tasks, as well as method {#link #submitAndCancelOnTimeout} to + * cancel tasks that take too long. The submitted functions or actions + * may create and invoke other {@linkplain ForkJoinTask + * ForkJoinTasks}. When time-based methods are used, shutdown + * policies are based on the default policies of class {@link + * ScheduledThreadPoolExecutor}: upon {@link #shutdown}, existing + * periodic tasks will not re-execute, and the pool terminates when + * quiescent and existing delayed tasks complete. + * *

The parameters used to construct the common pool may be controlled by * setting the following {@linkplain System#getProperty system properties}: *

    @@ -2716,14 +2727,14 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { if ((quiet = quiescent()) > 0) now = true; else if (quiet == 0 && (ds = delayScheduler) != null) - ds.activate(); + ds.ensureActive(); } if (now) { DelayScheduler ds; releaseWaiters(); if ((ds = delayScheduler) != null) - ds.activate(); + ds.ensureActive(); for (;;) { if (((e = runState) & CLEANED) == 0L) { boolean clean = cleanQueues(); @@ -2734,7 +2745,7 @@ else if (quiet == 0 && (ds = delayScheduler) != null) break; if (ctl != 0L) // else loop if didn't finish cleaning break; - if ((ds = delayScheduler) != null && !ds.activate()) + if ((ds = delayScheduler) != null && ds.ensureActive() >= 0) break; if ((e & CLEANED) != 0L) { e |= TERMINATED; @@ -3374,28 +3385,78 @@ public T invokeAny(Collection> tasks, .invokeAny(tasks, this, true, unit.toNanos(timeout)); } + /* + * The DelayScheduler maintains a binary heap based on trigger times + * (field DelayedTask.when) along with a pending queue of tasks + * submitted by other threads. When ready, tasks are pushed onto + * an external WorkQueue. + * + * To reduce memory contention, the heap is maintained solely via + * local variables in method loop() (forcing noticeable code + * sprawl), recording only the heap array to allow method + * canShutDown to conservatively check emptiness. + * + * 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. + * + * 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 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 tryTerminate checks as + * well as to suppress reactivation while terminating. + * + * The implementation is designed to accommodate usages in which + * many or even most tasks are cancelled before executing (mainly + * IO-based timeouts). Cancellations are added to the pending + * queue in method DelayedTask.cancel(), to remove them from the + * heap. (This requires some safeguards to deal with tasks + * cancelled while they are still pending.) In addition, + * cancelled tasks set their "when" fields to Long.MAX_VALUE, + * which causes them to be pushed toward the bottom of the heap + * where they can be simply swept out in the course of other add + * and replace operations, even before processing the removal + * request (which is then a no-op). + * + * To ensure that comparisons do not encounter integer wrap + * errors, times are offset with the most negative possible value + * (nanoTimeOffset) determined during static initialization, and + * negative delays are screened out in public submission methods + * + * For the sake of compatibility with ScheduledThreadPoolExecutor, + * shutdown follows the same rules, which add some further + * complexity beyond the cleanup associated with shutdownNow + * (runState STOP). Upon noticing pool shutdown, all periodic + * tasks are purged; the scheduler then triggers pool.tryTerminate + * when the heap is empty. The asynchronicity of these steps with + * respect to pool runState weakens guarantees about exactly when + * remaining tasks report isCancelled to callers (they do not run, + * but there may be a lag setting their status). + */ static final class DelayScheduler extends Thread { private static final int INITIAL_HEAP_CAPACITY = 1 << 6; - private final ForkJoinPool pool; - private DelayedTask[] heap; - private int heapSize; - @jdk.internal.vm.annotation.Contended() - private volatile int active; // 0: inactive, <0: stopped, >0: running + private final ForkJoinPool pool; // read only once + private DelayedTask[] heap; // written only when (re)allocated + private volatile int active; // 0: inactive, -1: stopped, +1: running @jdk.internal.vm.annotation.Contended() - private volatile DelayedTask additions; - @jdk.internal.vm.annotation.Contended() - private volatile DelayedTask removals; + private volatile DelayedTask pending; + private static final Unsafe U; private static final long ACTIVE; - private static final long ADDITIONS; - private static final long REMOVALS; + private static final long PENDING; static final long nanoTimeOffset; static { U = Unsafe.getUnsafe(); Class klass = DelayScheduler.class; ACTIVE = U.objectFieldOffset(klass, "active"); - ADDITIONS = U.objectFieldOffset(klass, "additions"); - REMOVALS = U.objectFieldOffset(klass, "removals"); + PENDING = U.objectFieldOffset(klass, "pending"); long ns = System.nanoTime(); // ensure negative to avoid overflow nanoTimeOffset = Long.MIN_VALUE + (ns < 0L ? ns : 0L); } @@ -3406,217 +3467,191 @@ static final class DelayScheduler extends Thread { pool = p; } + /** + * Returns System.nanoTime() with nanoTimeOffset + */ static final long now() { return nanoTimeOffset + System.nanoTime(); } - final boolean activate() { + /** + * Ensure active, unparking if necessary, unless stopped + */ + final int ensureActive() { int state; - if ((state = active) < 0) // stopped - return true; - if (state == 0 && U.getAndBitwiseOrInt(this, ACTIVE, 1) == 0) + if ((state = active) == 0 && U.getAndBitwiseOrInt(this, ACTIVE, 1) == 0) U.unpark(this); - return false; - } - - final boolean canShutDown() { - int state; DelayedTask[] h; - return ((state = active) < 0 || - (state == 0 && heapSize <= 0 && - ((h = heap) == null || h.length <= 0 || h[0] == null) && - active <= 0)); + return state; } - final void add(DelayedTask task) { - DelayedTask f = additions; + /** + * Inserts the task to pending queue, to add, remove, or ignore + * depending on task status when processed. + */ + final void pend(DelayedTask task) { + DelayedTask f = pending; if (task != null) { do {} while ( f != (f = (DelayedTask) U.compareAndExchangeReference( - this, ADDITIONS, task.nextPending = f, task))); - activate(); + this, PENDING, task.nextPending = f, task))); + ensureActive(); } } - final void remove(DelayedTask task) { - DelayedTask f = removals; - if (task != null) { - do {} while ( - f != (f = (DelayedTask) - U.compareAndExchangeReference( - this, REMOVALS, task.nextPending = f, task))); - activate(); - } + /** + * Returns true if (momentarily) inactive and heap is empty + */ + final boolean canShutDown() { + DelayedTask[] h; + return (active <= 0 && + ((h = heap) == null || h.length <= 0 || h[0] == null) && + active <= 0); } + /** + * Setup/teardown for scheduling loop + */ public final void run() { ForkJoinPool p; ThreadLocalRandom.localInit(); if ((p = pool) != null) { try { - WorkQueue q; // establish default submission queue - heap = new DelayedTask[INITIAL_HEAP_CAPACITY]; - if ((q = p.externalSubmissionQueue(false)) != null) { - q.unlockPhase(); - runScheduler(p); - cancelAll(); - } + loop(p); } finally { - active = 1 << 31; // set negative + active = -1; p.tryTerminate(false, false); } } } - private void runScheduler(ForkJoinPool p) { - if (p != null) { - boolean idle = false; - int canStop = 0; - long waitTime = 0L; - for (;;) { - long prs; - if (((prs = p.runState) & STOP) != 0L) - break; - else if ((prs & SHUTDOWN) != 0L && - (canStop = tryStopOnShutdown(p, canStop)) < 0) - break; - else if (idle) { - idle = false; - Thread.interrupted(); // clear - if (active == 0) { - U.park(false, waitTime); - active = 1; + /** + * After initialization, repeatedly: + * 1. Process pending tasks in batches, to add or remove from heap, + * 2. Check for shutdown, either exiting or preparing for shutdown when empty + * 3. Trigger all ready tasks by externally submitting them to pool + * 4. If active, set tentatively inactive, + * else park until next trigger time, or indefinitely if none + */ + private void loop(ForkJoinPool p) { + WorkQueue sq; DelayedTask[] h; + if ((sq = p.externalSubmissionQueue(false)) != null) + sq.unlockPhase(); // try creating default submission queue + heap = h = new DelayedTask[INITIAL_HEAP_CAPACITY]; + active = 1; + boolean purgedPeriodic = false; + for (int n = 0;;) { // n is heap size + DelayedTask t; + while (pending != null && // process pending tasks + (t = (DelayedTask) + U.getAndSetReference(this, PENDING, null)) != null) { + DelayedTask next; + int cap = h.length; + do { + int i = t.heapIndex; + long d = t.when; + if ((next = t.nextPending) != null) + t.nextPending = null; + if (i >= 0) { + t.heapIndex = -1; + if (i < n && i < cap && h[i] == t) + n = replace(h, i, n); } - } - else { - int state = active; - int hs = processPending(p, heap, heapSize); - waitTime = (hs > 0) ? submitReadyTasks(p, heap, hs) : 0L; - if (active == state) { - if (state != 0) - U.compareAndSetInt(this, ACTIVE, 1, 0); - else - idle = true; + else if (t.status >= 0) { + if (n >= cap || n < 0) // couldn't resize + t.trySetCancelled(); + else { + DelayedTask parent, u; int pk, newCap; + while (n > 0 && // clear trailing cancelled tasks + (u = h[n - 1]) != null && u.status < 0) { + u.heapIndex = -1; + h[--n] = null; + } + int k = n++; + while (k > 0 && // sift up + (parent = h[pk = (k - 1) >>> 1]) != 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) { + DelayedTask[] a = null; + try { // try to resize + a = Arrays.copyOf(heap, newCap); + } catch (Error | RuntimeException ex) { + } + if (a != null && (newCap = a.length) > cap) { + cap = newCap; + heap = h = a; + U.storeFence(); + } + } + } } - } + } while ((t = next) != null); } - } - } - private long submitReadyTasks(ForkJoinPool p, DelayedTask[] h, int hs) { - long waitTime = 0L, prs; int s; - if ((s = hs) > 0 && h != null && h.length > 0 && p != null && - ((prs = p.runState) & STOP) == 0L) { - for (long now = now();;) { - DelayedTask first; int stat; long d; - boolean cancel = false; - if ((first = h[0]) == null) - break; - if ((stat = first.status) >= 0) { - if (first.nextDelay != 0L && (prs & SHUTDOWN) != 0L) - cancel = true; - else if ((d = first.when - now) > 0L) { - waitTime = d; - break; - } - } - first.heapIndex = -1; - if (stat >= 0 && !cancel) { - try { - WorkQueue q; - if ((q = p.externalSubmissionQueue(false)) == null) - cancel = true; // terminating - else if (first.status < 0) - q.unlockPhase(); - else - q.push(first, p, false); - } catch(Error | RuntimeException ex) { - cancel = true; - } - } - if (cancel) - first.trySetCancelled(); - if ((s = heapReplace(h, 0, s)) == 0) + if ((p.runState & SHUTDOWN) != 0L) { + if ((n = tryStop(p, h, n, purgedPeriodic)) < 0) break; + purgedPeriodic = true; } - if (s != hs) - heapSize = s; - } - return waitTime; - } - private int processPending(ForkJoinPool p, DelayedTask[] h, int hs) { - int s = hs; - if (p != null && h != null) { - for (;;) { - DelayedTask t = - (removals != null) ? - (DelayedTask)U.getAndSetReference(this, REMOVALS, null): - (additions != null) ? - (DelayedTask)U.getAndSetReference(this, ADDITIONS, null): - null; - if (t == null) - break; - for (;;) { - int cap = h.length, idx; DelayedTask next; - if ((next = t.nextPending) != null) - t.nextPending = null; - if ((idx = t.heapIndex) >= 0) { - t.heapIndex = -1; - if (idx < s && idx < cap && h[idx] == t) - s = heapReplace(h, idx, s); - } - else if (s >= cap || s < 0 || // couldn't resize - (t.nextDelay != 0L && p.isShutdown())) - t.trySetCancelled(); - else if (t.status >= 0) { - DelayedTask u; int k, newCap; - while (s > 0 && // clear trailing cancelled tasks - (u = h[s - 1]) != null && u.status < 0) { - u.heapIndex = -1; - h[--s] = null; - } - if ((k = s++) > 0) { // sift up - for (long d = t.when;;) { - int pk; DelayedTask par; - if ((par = h[pk = (k - 1) >>> 1]) == null) - break; - if (par.when <= d && par.status >= 0) - break; - par.heapIndex = k; - h[k] = par; - if ((k = pk) == 0) - break; - } + long parkTime = 0L; // zero for untimed park + if (n > 0 && h.length > 0) { + long now = now(); + do { // submit ready tasks + DelayedTask f; int stat; + if ((f = h[0]) != null) { + long d = f.when - now; + if ((stat = f.status) >= 0 && d > 0L) { + parkTime = d; + break; } - t.heapIndex = k; - h[k] = t; - if (s >= cap && (newCap = cap << 1) > cap) { - DelayedTask[] a = null; - try { // try to resize - a = Arrays.copyOf(heap, newCap); - } catch (Error | RuntimeException ex) { + f.heapIndex = -1; + if (stat >= 0) { // else already cancelled + boolean cancel = false; + try { + WorkQueue q = p.externalSubmissionQueue(false); + if (q == null) // terminating + cancel = true; + else + q.push(f, p, false); + } catch(Error | RuntimeException ex) { + cancel = true; } - if (a != null) - cap = (heap = h = a).length; + if (cancel) + f.trySetCancelled(); } } - if ((t = next) == null) - break; - } + } while ((n = replace(h, 0, n)) > 0); + } + + if (pending == null) { + Thread.interrupted(); // clear before park + if (active == 0) + U.park(false, parkTime); + else + U.compareAndSetInt(this, ACTIVE, 1, 0); } } - if (s != hs) - heapSize = s; - return s; } - private static int heapReplace(DelayedTask[] h, int k, int s) { - if (k >= 0 && s > 0 && h != null && s <= h.length) { + /** + * Replaces removed heap element at index k + * @return current heap size + */ + private static int replace(DelayedTask[] h, int k, int n) { + if (k >= 0 && n > 0 && h != null && n <= h.length) { DelayedTask t = null, u; - while (--s > k) { // find uncancelled replacement - if ((u = h[s]) != null) { - h[s] = null; + 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; @@ -3625,15 +3660,12 @@ private static int heapReplace(DelayedTask[] h, int k, int s) { } } if (t != null) { - long d = t.when, rd; int ck, rk; DelayedTask c, r; - while ((ck = (k << 1) + 1) < s && (c = h[ck]) != null) { - long cw = c.when; - long cd = (c.status < 0) ? Long.MAX_VALUE : cw; - if ((rk = ck + 1) < s && (r = h[rk]) != null && - (rd = r.when) < cd && r.status >= 0) { - cd = rd; - c = r; - ck = rk; + int ck, rk; DelayedTask c, r; + while ((ck = (k << 1) + 1) < n && (c = h[ck]) != null) { + long cd = c.when, rd; + if ((rk = ck + 1) < n && (r = h[rk]) != null && + (rd = r.when) < cd) { + c = r; ck = rk; cd = rd; // use right child } if (d <= cd) break; @@ -3645,53 +3677,55 @@ private static int heapReplace(DelayedTask[] h, int k, int s) { } h[k] = t; } - return s; + return n; } - private int tryStopOnShutdown(ForkJoinPool p, int canStop) { - DelayedTask[] h; int s; - if ((s = heapSize) > 0 && canStop == 0 && (h = heap) != null && - h.length >= s) { // remove periodic tasks - DelayedTask t; int stat; - for (int i = 0; i < s && (t = h[s]) != null; ) { - if ((stat = t.status) < 0 || t.nextDelay != 0L) { - t.heapIndex = -1; - if (stat >= 0) - t.trySetCancelled(); - s = heapReplace(h, i, s); + /** + * Call only when pool is shutdown or stopping. If called when + * shutdown but not stopping, removes periodic tasks 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, DelayedTask[] h, + int n, boolean purgedPeriodic) { + if (p != null && h != null && h.length >= n) { + if (((p.runState & STOP) == 0L)) { + if (!purgedPeriodic && n > 0) { + DelayedTask 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.tryTerminate(false, false) & STOP) == 0L) + return n; } - heapSize = s; - } - return (s == 0 && active <= 0 && p != null && - (p.tryTerminate(false, false) & STOP) != 0L) ? -1 : 1; - } - - private void cancelAll() { - DelayedTask[] h; int s; - if ((s = heapSize) > 0 && (h = heap) != null && h.length >= s) { - heapSize = 0; - do { - DelayedTask u; - if ((u = h[--s]) != null) { - h[s] = null; - u.heapIndex = -1; + for (int i = 0; i < n; ++i) { + DelayedTask u = h[i]; + h[i] = null; + if (u != null) u.trySetCancelled(); - } - } while (s > 0); - } - DelayedTask t = (DelayedTask) - U.getAndSetReference(this, ADDITIONS, null); - if (t != null) { - do { - t.trySetCancelled(); - } while ((t = t.nextPending) != null); + } + for (DelayedTask a = (DelayedTask) + U.getAndSetReference(this, PENDING, null); + a != null; a = a.nextPending) + a.trySetCancelled(); // clear pending requests } - removals = null; + return -1; } } - private DelayScheduler startDelayScheduler() { + /** + * Common code for ScheduledExecutorService methods + */ + private void sched(DelayedTask t, long delay) { DelayScheduler ds; if ((ds = delayScheduler) == null) { boolean start = false; @@ -3713,24 +3747,15 @@ private DelayScheduler startDelayScheduler() { ds.start(); } } - return ds; - } - - private ScheduledFuture sched(Runnable command, Callable callable, - long delay, long nextDelay) { - DelayScheduler ds; - long when = DelayScheduler.now() + delay; - DelayedTask t = - new DelayedTask<>(command, callable, this, nextDelay, when); - if ((ds = delayScheduler) == null) - ds = startDelayScheduler(); - if (delay == 0L) - poolSubmit(true, t); - else if (ds == null || (runState & SHUTDOWN) != 0L) + if (ds == null || (runState & SHUTDOWN) != 0L) throw new RejectedExecutionException(); - else - ds.add(t); - return t; + if (t != null) { + t.when = DelayScheduler.now() + delay; + if (delay == 0L) + poolSubmit(true, t); + else + ds.pend(t); + } } public ScheduledFuture schedule(Runnable command, @@ -3738,8 +3763,10 @@ public ScheduledFuture schedule(Runnable command, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); - return sched(command, (Callable)null, - (delay <= 0L) ? 0L : unit.toNanos(delay), 0L); + long d = (delay <= 0L) ? 0L : unit.toNanos(delay); + DelayedTask t = new DelayedTask(command, null, this, 0L); + sched(t, d); + return t; } public ScheduledFuture schedule(Callable callable, @@ -3747,8 +3774,10 @@ public ScheduledFuture schedule(Callable callable, TimeUnit unit) { if (callable == null || unit == null) throw new NullPointerException(); - return sched(null, callable, - (delay <= 0L) ? 0L : unit.toNanos(delay), 0L); + long d = (delay <= 0L) ? 0L : unit.toNanos(delay); + DelayedTask t = new DelayedTask(null, callable, this, 0L); + sched(t, d); + return t; } public ScheduledFuture scheduleAtFixedRate(Runnable command, @@ -3759,9 +3788,11 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, throw new NullPointerException(); if (period <= 0L) throw new IllegalArgumentException(); - return sched(command, (Callable)null, - (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay), - -unit.toNanos(period)); + long d = (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay); + long p = -unit.toNanos(period); // negative for fixed rate + DelayedTask t = new DelayedTask(command, null, this, p); + sched(t, d); + return t; } public ScheduledFuture scheduleWithFixedDelay(Runnable command, @@ -3772,9 +3803,56 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, throw new NullPointerException(); if (delay <= 0L) throw new IllegalArgumentException(); - return sched(command, (Callable)null, - (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay), - unit.toNanos(delay)); + long d = (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay); + long p = unit.toNanos(delay); + DelayedTask t = new DelayedTask(command, null, this, p); + sched(t, d); + return t; + } + + /** + * Body of a DelayedTask serving to cancel another task on timeout + */ + static final class CancelAction implements Runnable { + ForkJoinTask task; // set after construction + public void run() { + ForkJoinTask t; + if ((t = task) != null) + t.cancel(true); + } + } + + /** + * Submits a task executing the given function, cancelling it (via + * {@code cancel(true)}) if not completed within the given timeout + * period. + * + * @param callable the function to execute + * @param the type of the callable's result + * @param timeout the time to wait before cancelling if not completed + * @param unit the time unit of the timeout parameter + * @return a Future that can be used to extract result or cancel + * @throws RejectedExecutionException if the task cannot be + * scheduled for execution + * @throws NullPointerException if callable or unit is null + * @throws IllegalArgumentException if timeout less than or equal to zero + */ + public ForkJoinTask submitAndCancelOnTimeout(Callable callable, + long timeout, + TimeUnit unit) { + ForkJoinTask.CallableWithCanceller task; CancelAction onTimeout; + if (callable == null || unit == null) + throw new NullPointerException(); + if (timeout <= 0L) + throw new IllegalArgumentException(); + long d = unit.toNanos(timeout); + DelayedTask canceller = new DelayedTask( + onTimeout = new CancelAction(), null, this, 0L); + onTimeout.task = task = + new ForkJoinTask.CallableWithCanceller(callable, canceller); + poolSubmit(true, task); + sched(canceller, d); + return task; } /** diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index c371f3632c357..7c9863ad38b24 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -331,14 +331,9 @@ private void setDone() { */ final int trySetCancelled() { int s; - for (;;) { - if ((s = status) < 0) - break; - if (casStatus(s, s | (DONE | ABNORMAL))) { - signalWaiters(); - break; - } - } + if ((s = status) >= 0 && + (s = getAndBitwiseOrStatus(DONE | ABNORMAL)) >= 0) + signalWaiters(); return s; } @@ -1661,17 +1656,19 @@ public final boolean exec() { return postExec(); } public boolean cancel(boolean mayInterruptIfRunning) { - Thread t; - if (trySetCancelled() >= 0) { + int s; boolean isCancelled; Thread t; + if ((s = trySetCancelled()) < 0) + isCancelled = ((s & (ABNORMAL | THROWN)) == ABNORMAL); + else { + isCancelled = true; if (mayInterruptIfRunning && (t = runner) != null) { try { t.interrupt(); } catch (Throwable ignore) { } } - return true; } - return isCancelled(); + return isCancelled; } public final void run() { quietlyInvoke(); } Object adaptee() { return null; } // for printing and diagnostics @@ -1879,13 +1876,12 @@ static final class DelayedTask extends InterruptibleTask int heapIndex; // index if queued on heap DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, - long nextDelay, long when) { - heapIndex = -1; - this.when = when; + long nextDelay) { this.runnable = runnable; this.callable = callable; this.pool = pool; this.nextDelay = nextDelay; + heapIndex = -1; } public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } @@ -1899,7 +1895,7 @@ final boolean postExec() { // resubmit if periodic when = ForkJoinPool.DelayScheduler.now() - d; else when += d; - ds.add(this); + ds.pend(this); return false; } trySetCancelled(); @@ -1917,19 +1913,65 @@ else if ((c = callable) != null) } final Object adaptee() { return (runnable != null) ? runnable : callable; } public final boolean cancel(boolean mayInterruptIfRunning) { + int s; boolean isCancelled; Thread t; ForkJoinPool p; ForkJoinPool.DelayScheduler ds; - boolean stat = super.cancel(mayInterruptIfRunning); - if (heapIndex >= 0 && nextPending == null && - (p = pool) != null && (ds = p.delayScheduler) != null) - ds.remove(this); // for heap cleanup - return stat; + if ((s = trySetCancelled()) < 0) + isCancelled = ((s & (ABNORMAL | THROWN)) == ABNORMAL); + else { + isCancelled = true; + if ((t = runner) != null) { + if (mayInterruptIfRunning) { + try { + t.interrupt(); + } catch (Throwable ignore) { + } + } + } + else if (heapIndex >= 0 && nextPending == null && + (p = pool) != null && (ds = p.delayScheduler) != null) { + when = Long.MAX_VALUE; // set to max delay + ds.pend(this); // for heap cleanup + } + } + return isCancelled; } public final long getDelay(TimeUnit unit) { - return unit.convert(when - ForkJoinPool.DelayScheduler.now(), NANOSECONDS); + return unit.convert(when - + ForkJoinPool.DelayScheduler.now(), NANOSECONDS); } public int compareTo(Delayed other) { long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; } } + + /** + * Adapter for Callable-based interruptible tasks with timeouts. + */ + @SuppressWarnings("serial") // Conditionally serializable + static final class CallableWithCanceller extends InterruptibleTask { + final Callable callable; + final ForkJoinTask canceller; + T result; + CallableWithCanceller(Callable callable, + ForkJoinTask canceller) { + this.callable = callable; + this.canceller = canceller; + } + public final T getRawResult() { return result; } + public final void setRawResult(T v) { result = v; } + final T compute() throws Exception { + try { + return callable.call(); + } finally { + ForkJoinTask t; // cancel the canceller + if ((t = canceller) != null) + t.cancel(false); + } + } + final boolean postExec() { return true; } + final Object adaptee() { return callable; } + private static final long serialVersionUID = 2838392045355241008L; + } + } From 49b1699fcb73f6141e4770d7bd3a83d6f85bbbd9 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Mon, 3 Feb 2025 11:43:57 -0500 Subject: [PATCH 17/40] Separate out DelayScheduler.java --- .../java/util/concurrent/DelayScheduler.java | 490 +++++++++++++++++ .../java/util/concurrent/ForkJoinPool.java | 495 ++++-------------- .../java/util/concurrent/ForkJoinTask.java | 83 --- 3 files changed, 601 insertions(+), 467 deletions(-) create mode 100644 src/java.base/share/classes/java/util/concurrent/DelayScheduler.java diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java new file mode 100644 index 0000000000000..085f2705409be --- /dev/null +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -0,0 +1,490 @@ +/* + * 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; +import static java.util.concurrent.ForkJoinTask.InterruptibleTask; + +/** + * An add-on for ForkJoinPools that provides scheduling for + * delayed and periodic tasks + */ +final class DelayScheduler extends Thread { + + /* + * The DelayScheduler maintains a binary heap based on trigger times + * (field DelayedTask.when) along with a pending queue of tasks + * submitted by other threads. When ready, tasks are relayed + * to the pool. + * + * To reduce memory contention, the heap is maintained solely via + * local variables in method loop() (forcing noticeable code + * sprawl), recording only the heap array to allow method + * canShutDown to conservatively check emptiness. + * + * 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. + * + * 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 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 tryTerminate checks as + * well as to suppress reactivation while terminating. + * + * The implementation is designed to accommodate usages in which + * many or even most tasks are cancelled before executing (mainly + * IO-based timeouts). Cancellations are added to the pending + * queue in method DelayedTask.cancel(), to remove them from the + * heap. (This requires some safeguards to deal with tasks + * cancelled while they are still pending.) In addition, + * cancelled tasks set their "when" fields to Long.MAX_VALUE, + * which causes them to be pushed toward the bottom of the heap + * where they can be simply swept out in the course of other add + * and replace operations, even before processing the removal + * request (which is then a no-op). + * + * To ensure that comparisons do not encounter integer wrap + * errors, times are offset with the most negative possible value + * (nanoTimeOffset) determined during static initialization, and + * negative delays are screened out in public submission methods + * + * For the sake of compatibility with ScheduledThreadPoolExecutor, + * shutdown follows the same rules, which add some further + * complexity beyond the cleanup associated with shutdownNow + * (runState STOP). Upon noticing pool shutdown, all periodic + * tasks are purged; the scheduler then triggers pool.tryTerminate + * when the heap is empty. The asynchronicity of these steps with + * respect to pool runState weakens guarantees about exactly when + * remaining tasks report isCancelled to callers (they do not run, + * but there may be a lag setting their status). + */ + + private static final int INITIAL_HEAP_CAPACITY = 1 << 6; + private final ForkJoinPool pool; // read only once + private DelayedTask[] heap; // written only when (re)allocated + private volatile int active; // 0: inactive, -1: stopped, +1: running + @jdk.internal.vm.annotation.Contended() + private volatile DelayedTask pending; + + private static final Unsafe U; + private static final long ACTIVE; + private static final long PENDING; + static final long nanoTimeOffset; + static { + U = Unsafe.getUnsafe(); + Class klass = DelayScheduler.class; + ACTIVE = U.objectFieldOffset(klass, "active"); + PENDING = U.objectFieldOffset(klass, "pending"); + long ns = System.nanoTime(); // ensure negative to avoid overflow + nanoTimeOffset = Long.MIN_VALUE + (ns < 0L ? ns : 0L); + } + + DelayScheduler(ForkJoinPool p, String name) { + super(name); + setDaemon(true); + pool = p; + } + + /** + * Returns System.nanoTime() with nanoTimeOffset + */ + static final long now() { + return nanoTimeOffset + System.nanoTime(); + } + + /** + * Ensure active, unparking if necessary, unless stopped + */ + final int ensureActive() { + int state; + if ((state = active) == 0 && U.getAndBitwiseOrInt(this, ACTIVE, 1) == 0) + U.unpark(this); + return state; + } + + /** + * Inserts the task to pending queue, to add, remove, or ignore + * depending on task status when processed. + */ + final void pend(DelayedTask task) { + DelayedTask f = pending; + if (task != null) { + do {} while ( + f != (f = (DelayedTask) + U.compareAndExchangeReference( + this, PENDING, task.nextPending = f, task))); + ensureActive(); + } + } + + /** + * Returns true if (momentarily) inactive and heap is empty + */ + final boolean canShutDown() { + DelayedTask[] h; + return (active <= 0 && + ((h = heap) == null || h.length <= 0 || h[0] == null) && + active <= 0); + } + + /** + * Setup and run scheduling loop + */ + public final void run() { + ForkJoinPool p; + ThreadLocalRandom.localInit(); + if ((p = pool) != null) { + try { + loop(p); + } finally { + active = -1; + ForkJoinPool.canTerminate(p); + } + } + } + + /** + * After initialization, repeatedly: + * 1. Process pending tasks in batches, to add or remove from heap, + * 2. Check for shutdown, either exiting or preparing for shutdown when empty + * 3. Trigger all ready tasks by externally submitting them to pool + * 4. If active, set tentatively inactive, + * else park until next trigger time, or indefinitely if none + */ + private void loop(ForkJoinPool p) { + DelayedTask[] h = new DelayedTask[INITIAL_HEAP_CAPACITY]; + heap = h; + active = 1; + boolean purgedPeriodic = false; + for (int n = 0;;) { // n is heap size + DelayedTask t; + while (pending != null && // process pending tasks + (t = (DelayedTask) + U.getAndSetReference(this, PENDING, null)) != null) { + DelayedTask next; + int cap = h.length; + do { + int i = t.heapIndex; + long d = t.when; + if ((next = t.nextPending) != null) + t.nextPending = null; + if (i >= 0) { + t.heapIndex = -1; + if (i < n && i < cap && h[i] == t) + n = replace(h, i, n); + } + else if (t.status >= 0) { + DelayedTask parent, u; int pk; DelayedTask[] nh; + if (n >= cap || n < 0) // couldn't resize + t.trySetCancelled(); + else { + while (n > 0 && // clear trailing cancellations + (u = h[n - 1]) != null && u.status < 0) { + u.heapIndex = -1; + h[--n] = null; + } + int k = n++; + while (k > 0 && // sift up + (parent = h[pk = (k - 1) >>> 1]) != null && + (parent.when > d)) { + parent.heapIndex = k; + h[k] = parent; + k = pk; + } + t.heapIndex = k; + h[k] = t; + if (n >= cap && (nh = growHeap(h)) != null) + cap = (h = nh).length; + } + } + } while ((t = next) != null); + } + + if (ForkJoinPool.poolIsShutdown(p)) { + if ((n = tryStop(p, h, n, purgedPeriodic)) < 0) + break; + purgedPeriodic = true; + } + + long parkTime = 0L; // zero for untimed park + if (n > 0 && h.length > 0) { + long now = now(); + do { // submit ready tasks + DelayedTask 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 && p != null) + p.executeReadyDelayedTask(f); + } + } while ((n = replace(h, 0, n)) > 0); + } + + if (pending == null) { + Thread.interrupted(); // clear before park + if (active == 0) + U.park(false, parkTime); + else + U.compareAndSetInt(this, ACTIVE, 1, 0); + } + } + } + + /** + * Tries to reallocate the heap array, returning existing + * array on failure. + */ + private DelayedTask[] growHeap(DelayedTask[] h) { + int cap, newCap; + if (h != null && (cap = h.length) < (newCap = cap << 1)) { + DelayedTask[] a = null; + try { + a = Arrays.copyOf(h, newCap); + } catch (Error | RuntimeException ex) { + } + if (a != null && a.length > cap) { + heap = h = a; + U.storeFence(); + } + } + return h; + } + + /** + * Replaces removed heap element at index k + * @return current heap size + */ + private static int replace(DelayedTask[] h, int k, int n) { + if (k >= 0 && n > 0 && h != null && n <= h.length) { + DelayedTask 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) { + int ck, rk; DelayedTask c, r; + while ((ck = (k << 1) + 1) < n && (c = h[ck]) != null) { + long cd = c.when, rd; + if ((rk = ck + 1) < n && (r = h[rk]) != null && + (rd = r.when) < cd) { + c = r; ck = rk; cd = rd; // use right child + } + if (d <= cd) + break; + c.heapIndex = k; + h[k] = c; + k = ck; + } + t.heapIndex = k; + } + h[k] = t; + } + return n; + } + + /** + * Call only when pool is shutdown or stopping. If called when + * shutdown but not stopping, removes periodic tasks 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, DelayedTask[] h, + int n, boolean purgedPeriodic) { + if (p != null && h != null && h.length >= n) { + if (!ForkJoinPool.poolIsStopping(p)) { + if (!purgedPeriodic && n > 0) { + DelayedTask 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 || !ForkJoinPool.canTerminate(p)) + return n; + } + for (int i = 0; i < n; ++i) { + DelayedTask u = h[i]; + h[i] = null; + if (u != null) + u.trySetCancelled(); + } + for (DelayedTask a = (DelayedTask) + U.getAndSetReference(this, PENDING, null); + a != null; a = a.nextPending) + a.trySetCancelled(); // clear pending requests + } + return -1; + } + + /** + * Returns an approximate count by finding the highest used + * heap array slot. This is very racy and not fast, but + * useful enough for monitoring purposes. + */ + final int approximateSize() { + DelayedTask[] h; + int size = 0; + if (active >= 0 && (h = heap) != null) { + for (int i = h.length - 1; i >= 0; --i) { + if (h[i] != null) { + size = i + 1; + break; + } + } + } + return size; + } + + /** + * Task class for DelayScheduler operations + */ + @SuppressWarnings("serial") + static final class DelayedTask extends InterruptibleTask + implements ScheduledFuture { + final Runnable runnable; // only one of runnable or callable nonnull + final Callable callable; + final ForkJoinPool pool; + T result; + DelayedTask nextPending; // for DelayScheduler submissions + final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate + long when; // nanoTime-based trigger time + int heapIndex; // if non-negative, index on heap + + DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, + long nextDelay, long delay) { + heapIndex = -1; + this.runnable = runnable; + this.callable = callable; + this.pool = pool; + this.nextDelay = nextDelay; + this.when = delay + DelayScheduler.now(); + } + 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 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() { // resubmit if periodic + long d; ForkJoinPool p; DelayScheduler ds; + if ((d = nextDelay) != 0L && status >= 0 && + (p = pool) != null && (ds = p.delayScheduler) != null) { + if (!ForkJoinPool.poolIsShutdown(p)) { + heapIndex = -1; + if (d < 0L) + when = DelayScheduler.now() - d; + else + when += d; + ds.pend(this); + return false; + } + trySetCancelled(); + } + return true; + } + + public final boolean cancel(boolean mayInterruptIfRunning) { + int s; boolean isCancelled; Thread t; + ForkJoinPool p; DelayScheduler ds; + if ((s = trySetCancelled()) < 0) + isCancelled = ((s & (ABNORMAL | THROWN)) == ABNORMAL); + else { + isCancelled = true; + if ((t = runner) != null) { + if (mayInterruptIfRunning) { + try { + t.interrupt(); + } catch (Throwable ignore) { + } + } + } + else if (heapIndex >= 0 && nextPending == null && + (p = pool) != null && (ds = p.delayScheduler) != null) { + when = Long.MAX_VALUE; // set to max delay + ds.pend(this); // for heap cleanup + } + } + return isCancelled; + } + + public final long getDelay(TimeUnit unit) { + return unit.convert(when - DelayScheduler.now(), NANOSECONDS); + } + public int compareTo(Delayed other) { // never used internally + long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); + return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; + } + } + +} + diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 9e8898e890229..04615433eedfd 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -52,7 +52,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.vm.SharedThreadContainer; -import static java.util.concurrent.ForkJoinTask.DelayedTask; +import static java.util.concurrent.DelayScheduler.DelayedTask; /** * An {@link ExecutorService} for running {@link ForkJoinTask}s. @@ -141,11 +141,12 @@ * tasks, as well as method {#link #submitAndCancelOnTimeout} to * cancel tasks that take too long. The submitted functions or actions * may create and invoke other {@linkplain ForkJoinTask - * ForkJoinTasks}. When time-based methods are used, shutdown - * policies are based on the default policies of class {@link - * ScheduledThreadPoolExecutor}: upon {@link #shutdown}, existing - * periodic tasks will not re-execute, and the pool terminates when - * quiescent and existing delayed tasks complete. + * ForkJoinTasks}. Resource exhaustian encountered after initial + * submission results in task cancellation. When time-based methods + * are used, shutdown policies are based on the default policies of + * class {@link ScheduledThreadPoolExecutor}: upon {@link #shutdown}, + * existing periodic tasks will not re-execute, and the pool + * terminates when quiescent and existing delayed tasks complete. * *

    The parameters used to construct the common pool may be controlled by * setting the following {@linkplain System#getProperty system properties}: @@ -230,6 +231,7 @@ public class ForkJoinPool extends AbstractExecutorService * 3. Completion-based tasks (mainly CountedCompleters) * 4. CommonPool and parallelStream support * 5. InterruptibleTasks for externally submitted tasks + * 6. Support ScheduledExecutorService methods * * Most changes involve adaptions of base algorithms using * combinations of static and dynamic bitwise mode settings (both @@ -895,6 +897,25 @@ public class ForkJoinPool extends AbstractExecutorService * writing, virtual thread bodies are by default run as some form * of InterruptibleTask. * + * DelayScheduler + * ================ + * + * This class supports ScheduledExecutorService methods by + * creating and starting a DelayScheduler on first use of these + * methods. The scheduler operates independently in its own + * thread, relaying tasks to the pool to execute as they become + * ready (see method executeReadyDelayedTask). The only other + * interactions with the delayScheduler are to control shutdown + * and maintain shutdown-related policies in methods quiescent() + * and tryTerminate(). In particular, to conform to policies, + * shutdown-related processing must deal with cases in which tasks + * are submitted before shutdown, but not ready until afterwards, + * in which case they must bypass some screening to be allowed to + * run. Conversely, the DelayScheduler interacts with the pool + * only to check runState status (via mehods poolIsStopping and + * poolIsShutdown) and complete termination (via canTerminate) + * that invoke corresponding private pool implementations. + * * Memory placement * ================ * @@ -1699,9 +1720,16 @@ private long spinLockRunState() { // spin/sleep } } - static boolean poolIsStopping(ForkJoinPool p) { // Used by ForkJoinTask + // status methods used by other classes in this package + static boolean poolIsStopping(ForkJoinPool p) { return p != null && (p.runState & STOP) != 0L; } + static boolean poolIsShutdown(ForkJoinPool p) { + return p != null && (p.runState & SHUTDOWN) != 0L; + } + static boolean canTerminate(ForkJoinPool p) { // true if terminated + return p != null && (p.tryTerminate(false, false) & STOP) != 0L; + } // Creating, registering, and deregistering workers @@ -2705,7 +2733,7 @@ static int getSurplusQueuedTaskCount() { * @param enable if true, terminate when next possible * @return runState on exit */ - final long tryTerminate(boolean now, boolean enable) { + private long tryTerminate(boolean now, boolean enable) { long e, isShutdown, ps; if (((e = runState) & TERMINATED) != 0L) now = false; @@ -3385,347 +3413,12 @@ public T invokeAny(Collection> tasks, .invokeAny(tasks, this, true, unit.toNanos(timeout)); } - /* - * The DelayScheduler maintains a binary heap based on trigger times - * (field DelayedTask.when) along with a pending queue of tasks - * submitted by other threads. When ready, tasks are pushed onto - * an external WorkQueue. - * - * To reduce memory contention, the heap is maintained solely via - * local variables in method loop() (forcing noticeable code - * sprawl), recording only the heap array to allow method - * canShutDown to conservatively check emptiness. - * - * 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. - * - * 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 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 tryTerminate checks as - * well as to suppress reactivation while terminating. - * - * The implementation is designed to accommodate usages in which - * many or even most tasks are cancelled before executing (mainly - * IO-based timeouts). Cancellations are added to the pending - * queue in method DelayedTask.cancel(), to remove them from the - * heap. (This requires some safeguards to deal with tasks - * cancelled while they are still pending.) In addition, - * cancelled tasks set their "when" fields to Long.MAX_VALUE, - * which causes them to be pushed toward the bottom of the heap - * where they can be simply swept out in the course of other add - * and replace operations, even before processing the removal - * request (which is then a no-op). - * - * To ensure that comparisons do not encounter integer wrap - * errors, times are offset with the most negative possible value - * (nanoTimeOffset) determined during static initialization, and - * negative delays are screened out in public submission methods - * - * For the sake of compatibility with ScheduledThreadPoolExecutor, - * shutdown follows the same rules, which add some further - * complexity beyond the cleanup associated with shutdownNow - * (runState STOP). Upon noticing pool shutdown, all periodic - * tasks are purged; the scheduler then triggers pool.tryTerminate - * when the heap is empty. The asynchronicity of these steps with - * respect to pool runState weakens guarantees about exactly when - * remaining tasks report isCancelled to callers (they do not run, - * but there may be a lag setting their status). - */ - static final class DelayScheduler extends Thread { - private static final int INITIAL_HEAP_CAPACITY = 1 << 6; - private final ForkJoinPool pool; // read only once - private DelayedTask[] heap; // written only when (re)allocated - private volatile int active; // 0: inactive, -1: stopped, +1: running - @jdk.internal.vm.annotation.Contended() - private volatile DelayedTask pending; - - private static final Unsafe U; - private static final long ACTIVE; - private static final long PENDING; - static final long nanoTimeOffset; - static { - U = Unsafe.getUnsafe(); - Class klass = DelayScheduler.class; - ACTIVE = U.objectFieldOffset(klass, "active"); - PENDING = U.objectFieldOffset(klass, "pending"); - long ns = System.nanoTime(); // ensure negative to avoid overflow - nanoTimeOffset = Long.MIN_VALUE + (ns < 0L ? ns : 0L); - } - - DelayScheduler(ForkJoinPool p, String name) { - super(name); - setDaemon(true); - pool = p; - } - - /** - * Returns System.nanoTime() with nanoTimeOffset - */ - static final long now() { - return nanoTimeOffset + System.nanoTime(); - } - - /** - * Ensure active, unparking if necessary, unless stopped - */ - final int ensureActive() { - int state; - if ((state = active) == 0 && U.getAndBitwiseOrInt(this, ACTIVE, 1) == 0) - U.unpark(this); - return state; - } - - /** - * Inserts the task to pending queue, to add, remove, or ignore - * depending on task status when processed. - */ - final void pend(DelayedTask task) { - DelayedTask f = pending; - if (task != null) { - do {} while ( - f != (f = (DelayedTask) - U.compareAndExchangeReference( - this, PENDING, task.nextPending = f, task))); - ensureActive(); - } - } - - /** - * Returns true if (momentarily) inactive and heap is empty - */ - final boolean canShutDown() { - DelayedTask[] h; - return (active <= 0 && - ((h = heap) == null || h.length <= 0 || h[0] == null) && - active <= 0); - } - - /** - * Setup/teardown for scheduling loop - */ - public final void run() { - ForkJoinPool p; - ThreadLocalRandom.localInit(); - if ((p = pool) != null) { - try { - loop(p); - } finally { - active = -1; - p.tryTerminate(false, false); - } - } - } - - /** - * After initialization, repeatedly: - * 1. Process pending tasks in batches, to add or remove from heap, - * 2. Check for shutdown, either exiting or preparing for shutdown when empty - * 3. Trigger all ready tasks by externally submitting them to pool - * 4. If active, set tentatively inactive, - * else park until next trigger time, or indefinitely if none - */ - private void loop(ForkJoinPool p) { - WorkQueue sq; DelayedTask[] h; - if ((sq = p.externalSubmissionQueue(false)) != null) - sq.unlockPhase(); // try creating default submission queue - heap = h = new DelayedTask[INITIAL_HEAP_CAPACITY]; - active = 1; - boolean purgedPeriodic = false; - for (int n = 0;;) { // n is heap size - DelayedTask t; - while (pending != null && // process pending tasks - (t = (DelayedTask) - U.getAndSetReference(this, PENDING, null)) != null) { - DelayedTask next; - int cap = h.length; - do { - int i = t.heapIndex; - long d = t.when; - if ((next = t.nextPending) != null) - t.nextPending = null; - if (i >= 0) { - t.heapIndex = -1; - if (i < n && i < cap && h[i] == t) - n = replace(h, i, n); - } - else if (t.status >= 0) { - if (n >= cap || n < 0) // couldn't resize - t.trySetCancelled(); - else { - DelayedTask parent, u; int pk, newCap; - while (n > 0 && // clear trailing cancelled tasks - (u = h[n - 1]) != null && u.status < 0) { - u.heapIndex = -1; - h[--n] = null; - } - int k = n++; - while (k > 0 && // sift up - (parent = h[pk = (k - 1) >>> 1]) != 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) { - DelayedTask[] a = null; - try { // try to resize - a = Arrays.copyOf(heap, newCap); - } catch (Error | RuntimeException ex) { - } - if (a != null && (newCap = a.length) > cap) { - cap = newCap; - heap = h = a; - U.storeFence(); - } - } - } - } - } while ((t = next) != null); - } - - if ((p.runState & SHUTDOWN) != 0L) { - if ((n = tryStop(p, h, n, purgedPeriodic)) < 0) - break; - purgedPeriodic = true; - } - - long parkTime = 0L; // zero for untimed park - if (n > 0 && h.length > 0) { - long now = now(); - do { // submit ready tasks - DelayedTask 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) { // else already cancelled - boolean cancel = false; - try { - WorkQueue q = p.externalSubmissionQueue(false); - if (q == null) // terminating - cancel = true; - else - q.push(f, p, false); - } catch(Error | RuntimeException ex) { - cancel = true; - } - if (cancel) - f.trySetCancelled(); - } - } - } while ((n = replace(h, 0, n)) > 0); - } - - if (pending == null) { - Thread.interrupted(); // clear before park - if (active == 0) - U.park(false, parkTime); - else - U.compareAndSetInt(this, ACTIVE, 1, 0); - } - } - } - - /** - * Replaces removed heap element at index k - * @return current heap size - */ - private static int replace(DelayedTask[] h, int k, int n) { - if (k >= 0 && n > 0 && h != null && n <= h.length) { - DelayedTask 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) { - int ck, rk; DelayedTask c, r; - while ((ck = (k << 1) + 1) < n && (c = h[ck]) != null) { - long cd = c.when, rd; - if ((rk = ck + 1) < n && (r = h[rk]) != null && - (rd = r.when) < cd) { - c = r; ck = rk; cd = rd; // use right child - } - if (d <= cd) - break; - c.heapIndex = k; - h[k] = c; - k = ck; - } - t.heapIndex = k; - } - h[k] = t; - } - return n; - } - - /** - * Call only when pool is shutdown or stopping. If called when - * shutdown but not stopping, removes periodic tasks 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, DelayedTask[] h, - int n, boolean purgedPeriodic) { - if (p != null && h != null && h.length >= n) { - if (((p.runState & STOP) == 0L)) { - if (!purgedPeriodic && n > 0) { - DelayedTask 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.tryTerminate(false, false) & STOP) == 0L) - return n; - } - for (int i = 0; i < n; ++i) { - DelayedTask u = h[i]; - h[i] = null; - if (u != null) - u.trySetCancelled(); - } - for (DelayedTask a = (DelayedTask) - U.getAndSetReference(this, PENDING, null); - a != null; a = a.nextPending) - a.trySetCancelled(); // clear pending requests - } - return -1; - } - } + // Support for delayed tasks /** - * Common code for ScheduledExecutorService methods + * Creates and starts DelayScheduler unless pool is in shutdown mode */ - private void sched(DelayedTask t, long delay) { + private DelayScheduler startDelayScheduler() { DelayScheduler ds; if ((ds = delayScheduler) == null) { boolean start = false; @@ -3747,36 +3440,68 @@ private void sched(DelayedTask t, long delay) { ds.start(); } } - if (ds == null || (runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - if (t != null) { - t.when = DelayScheduler.now() + delay; - if (delay == 0L) - poolSubmit(true, t); - else - ds.pend(t); + return ds; + } + + /** + * Arranges execution of a ready task from DelayScheduler + */ + final void executeReadyDelayedTask(DelayedTask task) { + if (task != null) { + WorkQueue q; + boolean cancel = false; + try { + if ((q = externalSubmissionQueue(false)) == null) + cancel = true; // terminating + else + q.push(task, this, false); + } catch(Error | RuntimeException ex) { + cancel = true; + } + if (cancel) + task.trySetCancelled(); + } + } + + /** + * Body of a DelayedTask serving to cancel another task on timeout + */ + static final class CancelAction implements Runnable { + ForkJoinTask task; // set after construction + public void run() { + ForkJoinTask t; + if ((t = task) != null) + t.cancel(true); } } public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + DelayScheduler ds; DelayedTask t; if (command == null || unit == null) throw new NullPointerException(); long d = (delay <= 0L) ? 0L : unit.toNanos(delay); - DelayedTask t = new DelayedTask(command, null, this, 0L); - sched(t, d); + if (((ds = delayScheduler) == null && + (ds = startDelayScheduler()) == null) || + (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + ds.pend(t = new DelayedTask(command, null, this, 0L, d)); return t; } public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + DelayScheduler ds; DelayedTask t; if (callable == null || unit == null) throw new NullPointerException(); long d = (delay <= 0L) ? 0L : unit.toNanos(delay); - DelayedTask t = new DelayedTask(null, callable, this, 0L); - sched(t, d); + if (((ds = delayScheduler) == null && + (ds = startDelayScheduler()) == null) || + (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + ds.pend(t = new DelayedTask(null, callable, this, 0L, d)); return t; } @@ -3784,14 +3509,18 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + DelayScheduler ds; DelayedTask t; if (command == null || unit == null) throw new NullPointerException(); if (period <= 0L) throw new IllegalArgumentException(); - long d = (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay); long p = -unit.toNanos(period); // negative for fixed rate - DelayedTask t = new DelayedTask(command, null, this, p); - sched(t, d); + long d = (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay); + if (((ds = delayScheduler) == null && + (ds = startDelayScheduler()) == null) || + (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + ds.pend(t = new DelayedTask(command, null, this, p, d)); return t; } @@ -3799,29 +3528,21 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + DelayScheduler ds; DelayedTask t; if (command == null || unit == null) throw new NullPointerException(); if (delay <= 0L) throw new IllegalArgumentException(); - long d = (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay); long p = unit.toNanos(delay); - DelayedTask t = new DelayedTask(command, null, this, p); - sched(t, d); + long d = (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay); + if (((ds = delayScheduler) == null && + (ds = startDelayScheduler()) == null) || + (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + ds.pend(t = new DelayedTask(command, null, this, p, d)); return t; } - /** - * Body of a DelayedTask serving to cancel another task on timeout - */ - static final class CancelAction implements Runnable { - ForkJoinTask task; // set after construction - public void run() { - ForkJoinTask t; - if ((t = task) != null) - t.cancel(true); - } - } - /** * Submits a task executing the given function, cancelling it (via * {@code cancel(true)}) if not completed within the given timeout @@ -3835,23 +3556,29 @@ public void run() { * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if callable or unit is null - * @throws IllegalArgumentException if timeout less than or equal to zero */ public ForkJoinTask submitAndCancelOnTimeout(Callable callable, long timeout, TimeUnit unit) { ForkJoinTask.CallableWithCanceller task; CancelAction onTimeout; + DelayScheduler ds; if (callable == null || unit == null) throw new NullPointerException(); - if (timeout <= 0L) - throw new IllegalArgumentException(); - long d = unit.toNanos(timeout); + long d = (timeout <= 0L) ? 0L : unit.toNanos(timeout); + if (((ds = delayScheduler) == null && + (ds = startDelayScheduler()) == null) || + (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); DelayedTask canceller = new DelayedTask( - onTimeout = new CancelAction(), null, this, 0L); + onTimeout = new CancelAction(), null, this, 0L, d); onTimeout.task = task = new ForkJoinTask.CallableWithCanceller(callable, canceller); - poolSubmit(true, task); - sched(canceller, d); + if (d == 0L) + task.trySetCancelled(); + else { + poolSubmit(true, task); + ds.pend(canceller); + } return task; } @@ -3997,7 +3724,7 @@ public long getStealCount() { */ public long getQueuedTaskCount() { WorkQueue[] qs; WorkQueue q; - int count = 0; + long count = 0; if ((runState & TERMINATED) == 0L && (qs = queues) != null) { for (int i = 1; i < qs.length; i += 2) { if ((q = qs[i]) != null) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 7c9863ad38b24..50462e33fe78e 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1863,88 +1863,6 @@ public final void setRawResult(Void v) { } final Object adaptee() { return callable; } } - @SuppressWarnings("serial") - static final class DelayedTask extends InterruptibleTask - implements ScheduledFuture { - final Runnable runnable; // only one of runnable or callable nonnull - final Callable callable; - final ForkJoinPool pool; - T result; - DelayedTask nextPending; // for DelayScheduler submissions - final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate - long when; // nanoTime-based trigger time - int heapIndex; // index if queued on heap - - DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, - long nextDelay) { - this.runnable = runnable; - this.callable = callable; - this.pool = pool; - this.nextDelay = nextDelay; - heapIndex = -1; - } - public final T getRawResult() { return result; } - public final void setRawResult(T v) { result = v; } - final boolean postExec() { // resubmit if periodic - long d; ForkJoinPool p; ForkJoinPool.DelayScheduler ds; - if ((d = nextDelay) != 0L && status >= 0 && - (p = pool) != null && (ds = p.delayScheduler) != null) { - if (!p.isShutdown()) { - heapIndex = -1; - if (d < 0L) - when = ForkJoinPool.DelayScheduler.now() - d; - else - when += d; - ds.pend(this); - return false; - } - trySetCancelled(); - } - return true; - } - final T compute() throws Exception { - Callable c; Runnable r; - T res = null; - if ((r = runnable) != null) - r.run(); - else if ((c = callable) != null) - res = c.call(); - return res; - } - final Object adaptee() { return (runnable != null) ? runnable : callable; } - public final boolean cancel(boolean mayInterruptIfRunning) { - int s; boolean isCancelled; Thread t; - ForkJoinPool p; ForkJoinPool.DelayScheduler ds; - if ((s = trySetCancelled()) < 0) - isCancelled = ((s & (ABNORMAL | THROWN)) == ABNORMAL); - else { - isCancelled = true; - if ((t = runner) != null) { - if (mayInterruptIfRunning) { - try { - t.interrupt(); - } catch (Throwable ignore) { - } - } - } - else if (heapIndex >= 0 && nextPending == null && - (p = pool) != null && (ds = p.delayScheduler) != null) { - when = Long.MAX_VALUE; // set to max delay - ds.pend(this); // for heap cleanup - } - } - return isCancelled; - } - public final long getDelay(TimeUnit unit) { - return unit.convert(when - - ForkJoinPool.DelayScheduler.now(), NANOSECONDS); - } - public int compareTo(Delayed other) { - long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); - return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; - } - } - /** * Adapter for Callable-based interruptible tasks with timeouts. */ @@ -1971,7 +1889,6 @@ final T compute() throws Exception { } final boolean postExec() { return true; } final Object adaptee() { return callable; } - private static final long serialVersionUID = 2838392045355241008L; } } From 229da14815fa011c9903e5b5fc184ed51d01dcd4 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Tue, 4 Feb 2025 15:48:51 -0500 Subject: [PATCH 18/40] Rework FJP-DS connections --- .../java/util/concurrent/DelayScheduler.java | 72 +++++++---------- .../java/util/concurrent/ForkJoinPool.java | 77 +++++++++++++------ 2 files changed, 80 insertions(+), 69 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index 085f2705409be..5517451f28fff 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -54,8 +54,10 @@ final class DelayScheduler extends Thread { * * To reduce memory contention, the heap is maintained solely via * local variables in method loop() (forcing noticeable code - * sprawl), recording only the heap array to allow method - * canShutDown to conservatively check emptiness. + * sprawl), recording only the current heap size when blocked to + * allow method canShutDown to conservatively check emptiness, and + * to support an approximate reporting of current size for + * monitoring. * * The pending queue uses a design similar to ForkJoinTask.Aux * queues: Incoming requests prepend (Treiber-stack-style) to the @@ -104,7 +106,7 @@ final class DelayScheduler extends Thread { private static final int INITIAL_HEAP_CAPACITY = 1 << 6; private final ForkJoinPool pool; // read only once - private DelayedTask[] heap; // written only when (re)allocated + int restingSize; // written only before parking private volatile int active; // 0: inactive, -1: stopped, +1: running @jdk.internal.vm.annotation.Contended() private volatile DelayedTask pending; @@ -164,24 +166,28 @@ final void pend(DelayedTask task) { * Returns true if (momentarily) inactive and heap is empty */ final boolean canShutDown() { - DelayedTask[] h; - return (active <= 0 && - ((h = heap) == null || h.length <= 0 || h[0] == null) && - active <= 0); + return (active <= 0 && restingSize <= 0); } /** - * Setup and run scheduling loop + * Returns an approximate number of elements in heap + */ + final int approximateSize() { + return (active < 0) ? 0 : restingSize; + } + + /** + * Sets up and runs scheduling loop */ public final void run() { ForkJoinPool p; - ThreadLocalRandom.localInit(); if ((p = pool) != null) { try { loop(p); } finally { + restingSize = 0; active = -1; - ForkJoinPool.canTerminate(p); + ForkJoinPool.poolCanTerminate(p); } } } @@ -195,9 +201,9 @@ public final void run() { * else park until next trigger time, or indefinitely if none */ private void loop(ForkJoinPool p) { - DelayedTask[] h = new DelayedTask[INITIAL_HEAP_CAPACITY]; - heap = h; + p.onDelaySchedulerStart(); active = 1; + DelayedTask[] h = new DelayedTask[INITIAL_HEAP_CAPACITY]; boolean purgedPeriodic = false; for (int n = 0;;) { // n is heap size DelayedTask t; @@ -236,7 +242,7 @@ else if (t.status >= 0) { } t.heapIndex = k; h[k] = t; - if (n >= cap && (nh = growHeap(h)) != null) + if (n >= cap && (nh = growHeap(h, cap)) != null) cap = (h = nh).length; } } @@ -261,13 +267,14 @@ else if (t.status >= 0) { break; } f.heapIndex = -1; - if (stat >= 0 && p != null) + if (stat >= 0) p.executeReadyDelayedTask(f); } } while ((n = replace(h, 0, n)) > 0); } if (pending == null) { + restingSize = n; Thread.interrupted(); // clear before park if (active == 0) U.park(false, parkTime); @@ -281,20 +288,16 @@ else if (t.status >= 0) { * Tries to reallocate the heap array, returning existing * array on failure. */ - private DelayedTask[] growHeap(DelayedTask[] h) { - int cap, newCap; - if (h != null && (cap = h.length) < (newCap = cap << 1)) { - DelayedTask[] a = null; + private DelayedTask[] growHeap(DelayedTask[] h, int cap) { + int newCap = cap << 1; + DelayedTask[] nh = h; + if (h != null && h.length == cap && cap < newCap) { try { - a = Arrays.copyOf(h, newCap); + nh = Arrays.copyOf(h, newCap); } catch (Error | RuntimeException ex) { } - if (a != null && a.length > cap) { - heap = h = a; - U.storeFence(); - } } - return h; + return nh; } /** @@ -361,7 +364,7 @@ private int tryStop(ForkJoinPool p, DelayedTask[] h, } } } - if (n > 0 || !ForkJoinPool.canTerminate(p)) + if (n > 0 || !ForkJoinPool.poolCanTerminate(p)) return n; } for (int i = 0; i < n; ++i) { @@ -378,25 +381,6 @@ private int tryStop(ForkJoinPool p, DelayedTask[] h, return -1; } - /** - * Returns an approximate count by finding the highest used - * heap array slot. This is very racy and not fast, but - * useful enough for monitoring purposes. - */ - final int approximateSize() { - DelayedTask[] h; - int size = 0; - if (active >= 0 && (h = heap) != null) { - for (int i = h.length - 1; i >= 0; --i) { - if (h[i] != null) { - size = i + 1; - break; - } - } - } - return size; - } - /** * Task class for DelayScheduler operations */ diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 04615433eedfd..aec071b7b7b29 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -902,19 +902,21 @@ public class ForkJoinPool extends AbstractExecutorService * * This class supports ScheduledExecutorService methods by * creating and starting a DelayScheduler on first use of these - * methods. The scheduler operates independently in its own - * thread, relaying tasks to the pool to execute as they become - * ready (see method executeReadyDelayedTask). The only other - * interactions with the delayScheduler are to control shutdown - * and maintain shutdown-related policies in methods quiescent() - * and tryTerminate(). In particular, to conform to policies, - * shutdown-related processing must deal with cases in which tasks - * are submitted before shutdown, but not ready until afterwards, - * in which case they must bypass some screening to be allowed to - * run. Conversely, the DelayScheduler interacts with the pool - * only to check runState status (via mehods poolIsStopping and - * poolIsShutdown) and complete termination (via canTerminate) - * that invoke corresponding private pool implementations. + * methods (via startDelayScheduler, with callback + * onDelaySchedulerStart). The scheduler operates independently in + * its own thread, relaying tasks to the pool to execute as they + * become ready (see method executeReadyDelayedTask). The only + * other interactions with the delayScheduler are to control + * shutdown and maintain shutdown-related policies in methods + * quiescent() and tryTerminate(). In particular, to conform to + * policies, shutdown-related processing must deal with cases in + * which tasks are submitted before shutdown, but not ready until + * afterwards, in which case they must bypass some screening to be + * allowed to run. Conversely, the DelayScheduler interacts with + * the pool only to check runState status (via mehods + * poolIsStopping and poolIsShutdown) and complete termination + * (via poolCanTerminate) that invoke corresponding private pool + * implementations. * * Memory placement * ================ @@ -1720,17 +1722,6 @@ private long spinLockRunState() { // spin/sleep } } - // status methods used by other classes in this package - static boolean poolIsStopping(ForkJoinPool p) { - return p != null && (p.runState & STOP) != 0L; - } - static boolean poolIsShutdown(ForkJoinPool p) { - return p != null && (p.runState & SHUTDOWN) != 0L; - } - static boolean canTerminate(ForkJoinPool p) { // true if terminated - return p != null && (p.tryTerminate(false, false) & STOP) != 0L; - } - // Creating, registering, and deregistering workers /** @@ -3415,6 +3406,17 @@ public T invokeAny(Collection> tasks, // Support for delayed tasks + // status methods used by Delayscheduler and other classes in this package + static boolean poolIsStopping(ForkJoinPool p) { + return p != null && (p.runState & STOP) != 0L; + } + static boolean poolIsShutdown(ForkJoinPool p) { + return p != null && (p.runState & SHUTDOWN) != 0L; + } + static boolean poolCanTerminate(ForkJoinPool p) { // true if terminated + return p != null && (p.tryTerminate(false, false) & STOP) != 0L; + } + /** * Creates and starts DelayScheduler unless pool is in shutdown mode */ @@ -3443,6 +3445,15 @@ private DelayScheduler startDelayScheduler() { return ds; } + /** + * Callback upon starting DelayScheduler + */ + final void onDelaySchedulerStart() { + WorkQueue q; // set up default submission queue + if ((q = submissionQueue(0, false)) != null) + q.unlockPhase(); + } + /** * Arranges execution of a ready task from DelayScheduler */ @@ -3753,6 +3764,19 @@ public int getQueuedSubmissionCount() { return count; } + /** + * Returns an estimate of the number of delayed (including + * periodic) tasks scheduled in this pool that are not yet ready + * to submit for execution. The returned value is innacurate when + * delayed tasks are being processed. + * + * @return an estimate of the number of delayed tasks + */ + public int getDelayedTaskCount() { + DelayScheduler ds; + return ((ds = delayScheduler) == null ? 0 : ds.approximateSize()); + } + /** * Returns {@code true} if there are any tasks submitted to this * pool that have not yet begun executing. @@ -3816,6 +3840,7 @@ protected int drainTasksTo(Collection> c) { */ public String toString() { // Use a single pass through queues to collect counts + DelayScheduler ds; long e = runState; long st = stealCount; long qt = 0L, ss = 0L; int rc = 0; @@ -3835,7 +3860,8 @@ public String toString() { } } } - + String delayed = ((ds = delayScheduler) == null ? "" : + ", delayed = " + ds.approximateSize()); int pc = parallelism; long c = ctl; int tc = (short)(c >>> TC_SHIFT); @@ -3855,6 +3881,7 @@ public String toString() { ", steals = " + st + ", tasks = " + qt + ", submissions = " + ss + + delayed + "]"; } From 9fad8d489e0dd6704c249da9c849182f3f918778 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Fri, 7 Feb 2025 10:25:46 -0500 Subject: [PATCH 19/40] Deal with commonPool parallelism zero; use in other juc classes; remove/adjust implementation assumptions in jtreg and tck tests --- .../util/concurrent/CompletableFuture.java | 64 ++++--------------- .../java/util/concurrent/DelayScheduler.java | 54 ++++++++-------- .../java/util/concurrent/ForkJoinPool.java | 55 ++++++++++++---- .../util/concurrent/SubmissionPublisher.java | 18 +----- ...tableFutureOrTimeoutExceptionallyTest.java | 29 ++------- .../concurrent/tck/CompletableFutureTest.java | 10 +-- .../concurrent/tck/ForkJoinPool20Test.java | 25 ++++++++ .../tck/SubmissionPublisherTest.java | 5 +- 8 files changed, 118 insertions(+), 142 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java index d690494405085..91b1c581af958 100644 --- a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java +++ b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java @@ -69,9 +69,8 @@ * a completion method. * *

  • All async methods without an explicit Executor - * argument are performed using the {@link ForkJoinPool#commonPool()} - * (unless it does not support a parallelism level of at least two, in - * which case, a new Thread is created to run each task). This may be + * argument are performed using the {@link ForkJoinPool#commonPool()}. + * This may be * overridden for non-static methods in subclasses by defining method * {@link #defaultExecutor()}. To simplify monitoring, debugging, * and tracking, all generated asynchronous tasks are instances of the @@ -473,31 +472,21 @@ private static Object reportJoin(Object r, String details) { public static interface AsynchronousCompletionTask { } - private static final boolean USE_COMMON_POOL = - (ForkJoinPool.getCommonPoolParallelism() > 1); - /** - * Default executor -- ForkJoinPool.commonPool() unless it cannot - * support parallelism. + * Default Executor */ - private static final Executor ASYNC_POOL = USE_COMMON_POOL ? - ForkJoinPool.commonPool() : new ThreadPerTaskExecutor(); + private static final ForkJoinPool ASYNC_POOL = + ForkJoinPool.asyncCommonPool(); - /** Fallback if ForkJoinPool.commonPool() cannot support parallelism */ - private static final class ThreadPerTaskExecutor implements Executor { - public void execute(Runnable r) { - Objects.requireNonNull(r); - new Thread(r).start(); - } + private static ScheduledFuture delay(Runnable command, long delay, + TimeUnit unit) { + return ASYNC_POOL.schedule(command, delay, unit); } /** - * Null-checks user executor argument, and translates uses of - * commonPool to ASYNC_POOL in case parallelism disabled. + * Null-checks user executor argument */ static Executor screenExecutor(Executor e) { - if (!USE_COMMON_POOL && e == ForkJoinPool.commonPool()) - return ASYNC_POOL; if (e == null) throw new NullPointerException(); return e; } @@ -2820,8 +2809,8 @@ public CompletableFuture orTimeout(long timeout, TimeUnit unit) { if (unit == null) throw new NullPointerException(); if (result == null) - whenComplete(new Canceller(Delayer.delay(new Timeout(this), - timeout, unit))); + whenComplete(new Canceller(delay(new Timeout(this), + timeout, unit))); return this; } @@ -2842,7 +2831,7 @@ public CompletableFuture completeOnTimeout(T value, long timeout, if (unit == null) throw new NullPointerException(); if (result == null) - whenComplete(new Canceller(Delayer.delay( + whenComplete(new Canceller(delay( new DelayedCompleter(this, value), timeout, unit))); return this; @@ -2929,33 +2918,6 @@ public static CompletionStage failedStage(Throwable ex) { return new MinimalStage(new AltResult(ex)); } - /** - * Singleton delay scheduler, used only for starting and - * cancelling tasks. - */ - static final class Delayer { - static ScheduledFuture delay(Runnable command, long delay, - TimeUnit unit) { - return delayer.schedule(command, delay, unit); - } - - static final class DaemonThreadFactory implements ThreadFactory { - public Thread newThread(Runnable r) { - Thread t = new Thread(r); - t.setDaemon(true); - t.setName("CompletableFutureDelayScheduler"); - return t; - } - } - - static final ScheduledThreadPoolExecutor delayer; - static { - (delayer = new ScheduledThreadPoolExecutor( - 1, new DaemonThreadFactory())). - setRemoveOnCancelPolicy(true); - } - } - // Little class-ified lambdas to better support monitoring static final class DelayedExecutor implements Executor { @@ -2966,7 +2928,7 @@ static final class DelayedExecutor implements Executor { this.delay = delay; this.unit = unit; this.executor = executor; } public void execute(Runnable r) { - Delayer.delay(new TaskSubmitter(executor, r), delay, unit); + delay(new TaskSubmitter(executor, r), delay, unit); } } diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index 5517451f28fff..bef067771c00d 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -38,7 +38,6 @@ import java.util.Arrays; import jdk.internal.misc.Unsafe; import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.ForkJoinTask.InterruptibleTask; /** * An add-on for ForkJoinPools that provides scheduling for @@ -47,17 +46,16 @@ final class DelayScheduler extends Thread { /* - * The DelayScheduler maintains a binary heap based on trigger times + * A DelayScheduler maintains a binary heap based on trigger times * (field DelayedTask.when) along with a pending queue of tasks - * submitted by other threads. When ready, tasks are relayed - * to the pool. + * submitted by other threads. When ready, tasks are relayed to + * the pool. * * 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 support an approximate reporting of current size for - * monitoring. + * 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 @@ -86,22 +84,23 @@ final class DelayScheduler extends Thread { * which causes them to be pushed toward the bottom of the heap * where they can be simply swept out in the course of other add * and replace operations, even before processing the removal - * request (which is then a no-op). + * request (which is then a no-op). In the mean time, these + * elements might harmlessly be out of numerical heap order. * * To ensure that comparisons do not encounter integer wrap * errors, times are offset with the most negative possible value - * (nanoTimeOffset) determined during static initialization, and - * negative delays are screened out in public submission methods + * (nanoTimeOffset) determined during static initialization. + * Negative delays must be screened out in public submission methods * * For the sake of compatibility with ScheduledThreadPoolExecutor, - * shutdown follows the same rules, which add some further - * complexity beyond the cleanup associated with shutdownNow - * (runState STOP). Upon noticing pool shutdown, all periodic - * tasks are purged; the scheduler then triggers pool.tryTerminate - * when the heap is empty. The asynchronicity of these steps with - * respect to pool runState weakens guarantees about exactly when - * remaining tasks report isCancelled to callers (they do not run, - * but there may be a lag setting their status). + * shutdown follows the same rules, which add some further steps + * beyond the cleanup associated with shutdownNow. Upon noticing + * pool shutdown, all periodic tasks are purged; 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). */ private static final int INITIAL_HEAP_CAPACITY = 1 << 6; @@ -257,18 +256,17 @@ else if (t.status >= 0) { long parkTime = 0L; // zero for untimed park if (n > 0 && h.length > 0) { - long now = now(); do { // submit ready tasks DelayedTask f; int stat; if ((f = h[0]) != null) { - long d = f.when - now; + long d = f.when - now(); if ((stat = f.status) >= 0 && d > 0L) { parkTime = d; break; } f.heapIndex = -1; if (stat >= 0) - p.executeReadyDelayedTask(f); + p.executeReadyDelayedTask(f, f.isImmediate); } } while ((n = replace(h, 0, n)) > 0); } @@ -341,15 +339,14 @@ private static int replace(DelayedTask[] h, int k, int n) { } /** - * Call only when pool is shutdown or stopping. If called when - * shutdown but not stopping, removes periodic tasks if not - * already done so, and if not empty or pool not terminating, - * returns. Otherwise, cancels all tasks in heap and pending - * queue. + * Call only when pool is shutdown. If called when not stopping, + * removes periodic tasks 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, DelayedTask[] h, - int n, boolean purgedPeriodic) { + private int tryStop(ForkJoinPool p, DelayedTask[] h, int n, + boolean purgedPeriodic) { if (p != null && h != null && h.length >= n) { if (!ForkJoinPool.poolIsStopping(p)) { if (!purgedPeriodic && n > 0) { @@ -385,7 +382,7 @@ private int tryStop(ForkJoinPool p, DelayedTask[] h, * Task class for DelayScheduler operations */ @SuppressWarnings("serial") - static final class DelayedTask extends InterruptibleTask + static final class DelayedTask extends ForkJoinTask.InterruptibleTask implements ScheduledFuture { final Runnable runnable; // only one of runnable or callable nonnull final Callable callable; @@ -395,6 +392,7 @@ static final class DelayedTask extends InterruptibleTask final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate long when; // nanoTime-based trigger time int heapIndex; // if non-negative, index on heap + boolean isImmediate; // true if action performed by scheduler DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, long nextDelay, long delay) { diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index aec071b7b7b29..3ebaaf693fde8 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -138,15 +138,20 @@ * *

    Additionally, this class supports {@link * ScheduledExecutorService} methods to delay or periodically execute - * tasks, as well as method {#link #submitAndCancelOnTimeout} to - * cancel tasks that take too long. The submitted functions or actions + * tasks, as well as method {#link #submitWithTimeout} to + * cancel tasks that take too long. The scheduled functions or actions * may create and invoke other {@linkplain ForkJoinTask - * ForkJoinTasks}. Resource exhaustian encountered after initial + * ForkJoinTasks}. Resource exhaustion encountered after initial * submission results in task cancellation. When time-based methods * are used, shutdown policies are based on the default policies of * class {@link ScheduledThreadPoolExecutor}: upon {@link #shutdown}, * existing periodic tasks will not re-execute, and the pool - * terminates when quiescent and existing delayed tasks complete. + * terminates when quiescent and existing delayed tasks + * complete. Method {@link #shutdownNow} may be used to instead + * unconditionally initiate pool termination. Monitoring methods such + * as {@link getQueuedTaskCount} do not include scheduled tasks that + * are not yet ready to execute, whcih are reported separately by + * method {@link getDelayedTaskCount}. * *

    The parameters used to construct the common pool may be controlled by * setting the following {@linkplain System#getProperty system properties}: @@ -843,7 +848,10 @@ public class ForkJoinPool extends AbstractExecutorService * fashion or use CountedCompleters (as is true for jdk * parallelStreams). Support infiltrates several methods, * including those that retry helping steps until we are sure that - * none apply if there are no workers. + * none apply if there are no workers. To deal with conflicting + * requirements, uses of the commonPool that require async because + * caller-runs does not apply, ensure at least one thread (method + * asyncCommonPool) before proceeding. * * As a more appropriate default in managed environments, unless * overridden by system properties, we use workers of subclass @@ -3416,6 +3424,12 @@ static boolean poolIsShutdown(ForkJoinPool p) { static boolean poolCanTerminate(ForkJoinPool p) { // true if terminated return p != null && (p.tryTerminate(false, false) & STOP) != 0L; } + static ForkJoinPool asyncCommonPool() { // override parallelism == 0 + ForkJoinPool cp; + if ((cp = common).parallelism == 0) + U.compareAndSetInt(cp, PARALLELISM, 0, 1); + return cp; + } /** * Creates and starts DelayScheduler unless pool is in shutdown mode @@ -3436,6 +3450,8 @@ private DelayScheduler startDelayScheduler() { } if (start) { // start outside of lock SharedThreadContainer ctr; + if (this == common) + asyncCommonPool(); if ((ctr = container) != null) ctr.start(ds); else @@ -3457,12 +3473,14 @@ final void onDelaySchedulerStart() { /** * Arranges execution of a ready task from DelayScheduler */ - final void executeReadyDelayedTask(DelayedTask task) { + final void executeReadyDelayedTask(DelayedTask task, boolean immediate) { if (task != null) { WorkQueue q; boolean cancel = false; try { - if ((q = externalSubmissionQueue(false)) == null) + if (immediate) + task.doExec(); + else if ((q = externalSubmissionQueue(false)) == null) cancel = true; // terminating else q.push(task, this, false); @@ -3557,7 +3575,17 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, /** * Submits a task executing the given function, cancelling it (via * {@code cancel(true)}) if not completed within the given timeout - * period. + * period. If you would like to use some other value in the event + * of cancellation and/or other errors, you could use a construction + * such as the following for a submitted {@code task}. + * + *

     {@code
    +     * T result;
    +     * try {
    +     *  result = task.get();
    +     * ) catch (Error | RuntimeException ex) {
    +     *    result = replacementValue;
    +     * }}}
    * * @param callable the function to execute * @param the type of the callable's result @@ -3568,9 +3596,9 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, * scheduled for execution * @throws NullPointerException if callable or unit is null */ - public ForkJoinTask submitAndCancelOnTimeout(Callable callable, - long timeout, - TimeUnit unit) { + public ForkJoinTask submitWithTimeout(Callable callable, + long timeout, + TimeUnit unit) { ForkJoinTask.CallableWithCanceller task; CancelAction onTimeout; DelayScheduler ds; if (callable == null || unit == null) @@ -3582,6 +3610,7 @@ public ForkJoinTask submitAndCancelOnTimeout(Callable callable, throw new RejectedExecutionException(); DelayedTask canceller = new DelayedTask( onTimeout = new CancelAction(), null, this, 0L, d); + canceller.isImmediate = true; onTimeout.task = task = new ForkJoinTask.CallableWithCanceller(callable, canceller); if (d == 0L) @@ -3728,7 +3757,9 @@ public long getStealCount() { * to the pool that have not begun executing). This value is only * an approximation, obtained by iterating across all threads in * the pool. This method may be useful for tuning task - * granularities. + * granularities.The returned count does not include scheduled + * tasks that are not yet ready to execute, which are reported + * separately by method {@link getDelayedTaskCount}. * * @return the number of queued tasks * @see ForkJoinWorkerThread#getQueuedTaskCount() diff --git a/src/java.base/share/classes/java/util/concurrent/SubmissionPublisher.java b/src/java.base/share/classes/java/util/concurrent/SubmissionPublisher.java index a2fc3ac92bdbc..3536b09d98271 100644 --- a/src/java.base/share/classes/java/util/concurrent/SubmissionPublisher.java +++ b/src/java.base/share/classes/java/util/concurrent/SubmissionPublisher.java @@ -206,22 +206,6 @@ static final int roundCapacity(int cap) { (n >= BUFFER_CAPACITY_LIMIT) ? BUFFER_CAPACITY_LIMIT : n + 1; } - // default Executor setup; nearly the same as CompletableFuture - - /** - * Default executor -- ForkJoinPool.commonPool() unless it cannot - * support parallelism. - */ - private static final Executor ASYNC_POOL = - (ForkJoinPool.getCommonPoolParallelism() > 1) ? - ForkJoinPool.commonPool() : new ThreadPerTaskExecutor(); - - /** Fallback if ForkJoinPool.commonPool() cannot support parallelism */ - private static final class ThreadPerTaskExecutor implements Executor { - ThreadPerTaskExecutor() {} // prevent access constructor creation - public void execute(Runnable r) { new Thread(r).start(); } - } - /** * Clients (BufferedSubscriptions) are maintained in a linked list * (via their "next" fields). This works well for publish loops. @@ -316,7 +300,7 @@ public SubmissionPublisher(Executor executor, int maxBufferCapacity) { * Flow.Subscriber#onNext(Object) onNext}. */ public SubmissionPublisher() { - this(ASYNC_POOL, Flow.defaultBufferSize(), null); + this(ForkJoinPool.asyncCommonPool(), Flow.defaultBufferSize(), null); } /** diff --git a/test/jdk/java/util/concurrent/CompletableFuture/CompletableFutureOrTimeoutExceptionallyTest.java b/test/jdk/java/util/concurrent/CompletableFuture/CompletableFutureOrTimeoutExceptionallyTest.java index 9c6b5b6e5cdc8..26fcceb66bf6a 100644 --- a/test/jdk/java/util/concurrent/CompletableFuture/CompletableFutureOrTimeoutExceptionallyTest.java +++ b/test/jdk/java/util/concurrent/CompletableFuture/CompletableFutureOrTimeoutExceptionallyTest.java @@ -30,39 +30,24 @@ */ import java.time.Duration; -import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTrue; class CompletableFutureOrTimeoutExceptionallyTest { - static final BlockingQueue delayerQueue; - static { - try { - var delayerClass = Class.forName("java.util.concurrent.CompletableFuture$Delayer", - true, - CompletableFuture.class.getClassLoader()); - var delayerField = delayerClass.getDeclaredField("delayer"); - delayerField.setAccessible(true); - delayerQueue = ((ScheduledThreadPoolExecutor)delayerField.get(null)).getQueue(); - } catch (Throwable t) { - throw new ExceptionInInitializerError(t); - } - } - + // updated February 2025 to adapt to CompletableFuture DelayScheduler changes /** * Test that orTimeout task is cancelled if the CompletableFuture is completed Exceptionally */ @Test void testOrTimeoutWithCompleteExceptionallyDoesNotLeak() throws InterruptedException { - assertTrue(delayerQueue.peek() == null); + ForkJoinPool delayer = ForkJoinPool.commonPool(); var future = new CompletableFuture<>().orTimeout(12, TimeUnit.HOURS); - assertTrue(delayerQueue.peek() != null); future.completeExceptionally(new RuntimeException("This is fine")); - while (delayerQueue.peek() != null) { + while (delayer.getDelayedTaskCount() != 0) { Thread.sleep(100); }; } @@ -72,12 +57,12 @@ void testOrTimeoutWithCompleteExceptionallyDoesNotLeak() throws InterruptedExcep */ @Test void testCompleteOnTimeoutWithCompleteExceptionallyDoesNotLeak() throws InterruptedException { - assertTrue(delayerQueue.peek() == null); + ForkJoinPool delayer = ForkJoinPool.commonPool(); var future = new CompletableFuture<>().completeOnTimeout(null, 12, TimeUnit.HOURS); - assertTrue(delayerQueue.peek() != null); future.completeExceptionally(new RuntimeException("This is fine")); - while (delayerQueue.peek() != null) { + while (delayer.getDelayedTaskCount() != 0) { Thread.sleep(100); }; } + } diff --git a/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java b/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java index 60da228d17592..4f2f5d2d805ad 100644 --- a/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java +++ b/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java @@ -658,8 +658,6 @@ public void execute(Runnable r) { } } - static final boolean defaultExecutorIsCommonPool - = ForkJoinPool.getCommonPoolParallelism() > 1; /** * Permits the testing of parallel code for the 3 different @@ -750,8 +748,7 @@ public CompletableFuture supplyAsync(Supplier a) { }, ASYNC { public void checkExecutionMode() { - mustEqual(defaultExecutorIsCommonPool, - (ForkJoinPool.commonPool() == ForkJoinTask.getPool())); + mustEqual(ForkJoinPool.commonPool(), ForkJoinTask.getPool()); } public CompletableFuture runAsync(Runnable a) { return CompletableFuture.runAsync(a); @@ -3794,10 +3791,7 @@ public void testDefaultExecutor() { CompletableFuture f = new CompletableFuture<>(); Executor e = f.defaultExecutor(); Executor c = ForkJoinPool.commonPool(); - if (ForkJoinPool.getCommonPoolParallelism() > 1) - assertSame(e, c); - else - assertNotSame(e, c); + assertSame(e, c); } /** diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index 71a01debc7de0..ddfaa65cf3cdb 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -565,4 +565,29 @@ public void testShutdownNow_delayedTasks() throws InterruptedException { assertTrue(p.isTerminated()); } + /** + * submitWithTimeout cancels task after timeout + */ + public void testSubmitWithTimeoutCancels() throws InterruptedException { + final ForkJoinPool p = ForkJoinPool.commonPool(); + Callable c = new Callable() { + public Boolean call() throws Exception { + Thread.sleep(LONGER_DELAY_MS); return Boolean.TRUE; }}; + ForkJoinTask task = p.submitWithTimeout(c, 1, NANOSECONDS); + Thread.sleep(timeoutMillis()); + assertTrue(task.isCancelled()); + } + /** + * submitWithTimeout doesn't cancel if completed before timeout + */ + public void testSubmitWithTimeout_NoTimeout() throws InterruptedException { + final ForkJoinPool p = ForkJoinPool.commonPool(); + Callable c = new Callable() { + public Boolean call() throws Exception { + return Boolean.TRUE; }}; + ForkJoinTask task = p.submitWithTimeout(c, LONGER_DELAY_MS, MILLISECONDS); + Thread.sleep(timeoutMillis()); + assertFalse(task.isCancelled()); + } + } diff --git a/test/jdk/java/util/concurrent/tck/SubmissionPublisherTest.java b/test/jdk/java/util/concurrent/tck/SubmissionPublisherTest.java index c47e66c6727f5..9f87ff5cf64be 100644 --- a/test/jdk/java/util/concurrent/tck/SubmissionPublisherTest.java +++ b/test/jdk/java/util/concurrent/tck/SubmissionPublisherTest.java @@ -178,10 +178,7 @@ public void testConstructor1() { checkInitialState(p); assertEquals(p.getMaxBufferCapacity(), Flow.defaultBufferSize()); Executor e = p.getExecutor(), c = ForkJoinPool.commonPool(); - if (ForkJoinPool.getCommonPoolParallelism() > 1) - assertSame(e, c); - else - assertNotSame(e, c); + assertSame(e, c); } /** From 2e60dc9e0439fe8c8944a04db87f8b8ec1bf0e5f Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sat, 8 Feb 2025 09:36:36 -0500 Subject: [PATCH 20/40] Refactor schedule methods --- .../java/util/concurrent/DelayScheduler.java | 29 +++-- .../java/util/concurrent/ForkJoinPool.java | 111 +++++++----------- 2 files changed, 64 insertions(+), 76 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index bef067771c00d..ccb5a8f6b501f 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -265,8 +265,12 @@ else if (t.status >= 0) { break; } f.heapIndex = -1; - if (stat >= 0) - p.executeReadyDelayedTask(f, f.isImmediate); + if (stat >= 0) { + if (f.isImmediate) + f.doExec(); + else + p.executeReadyDelayedTask(f); + } } } while ((n = replace(h, 0, n)) > 0); } @@ -389,20 +393,27 @@ static final class DelayedTask extends ForkJoinTask.InterruptibleTask final ForkJoinPool pool; T result; DelayedTask nextPending; // for DelayScheduler submissions - final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate - long when; // nanoTime-based trigger time - int heapIndex; // if non-negative, index on heap - boolean isImmediate; // true if action performed by scheduler + final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate + long when; // nanoTime-based trigger time + int heapIndex; // if non-negative, index on heap + final boolean isImmediate; // run by scheduler vs submitted when ready - DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, - long nextDelay, long delay) { + public DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, + boolean isImmediate, long nextDelay, long delay) { heapIndex = -1; this.runnable = runnable; this.callable = callable; this.pool = pool; + this.isImmediate = isImmediate; this.nextDelay = nextDelay; - this.when = delay + DelayScheduler.now(); + this.when = delay; // offset by DelayScheduler.now on schedule() + } + + public final void schedule() { + when += DelayScheduler.now(); + pool.scheduleDelayedTask(this); } + public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } final Object adaptee() { return (runnable != null) ? runnable : callable; } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 3ebaaf693fde8..dd21237defe19 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -910,7 +910,7 @@ public class ForkJoinPool extends AbstractExecutorService * * This class supports ScheduledExecutorService methods by * creating and starting a DelayScheduler on first use of these - * methods (via startDelayScheduler, with callback + * methods (via scheduleDelayedTask, with callback * onDelaySchedulerStart). The scheduler operates independently in * its own thread, relaying tasks to the pool to execute as they * become ready (see method executeReadyDelayedTask). The only @@ -3432,16 +3432,17 @@ static ForkJoinPool asyncCommonPool() { // override parallelism == 0 } /** - * Creates and starts DelayScheduler unless pool is in shutdown mode + * Arrange delayed execution of a DelayedTask via the + * DelayScheduler, creating and starting it if necessary. */ - private DelayScheduler startDelayScheduler() { + final void scheduleDelayedTask(DelayedTask task) { DelayScheduler ds; if ((ds = delayScheduler) == null) { boolean start = false; String name = poolName + "-delayScheduler"; - long rs = lockRunState(); + lockRunState(); try { - if ((rs & SHUTDOWN) == 0 && (ds = delayScheduler) == null) { + if ((ds = delayScheduler) == null) { ds = delayScheduler = new DelayScheduler(this, name); start = true; } @@ -3450,7 +3451,7 @@ private DelayScheduler startDelayScheduler() { } if (start) { // start outside of lock SharedThreadContainer ctr; - if (this == common) + if (this == common) // override parallelism 0 asyncCommonPool(); if ((ctr = container) != null) ctr.start(ds); @@ -3458,7 +3459,9 @@ private DelayScheduler startDelayScheduler() { ds.start(); } } - return ds; + if (ds == null || task == null || (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + ds.pend(task); } /** @@ -3473,14 +3476,12 @@ final void onDelaySchedulerStart() { /** * Arranges execution of a ready task from DelayScheduler */ - final void executeReadyDelayedTask(DelayedTask task, boolean immediate) { + final void executeReadyDelayedTask(DelayedTask task) { if (task != null) { WorkQueue q; boolean cancel = false; try { - if (immediate) - task.doExec(); - else if ((q = externalSubmissionQueue(false)) == null) + if ((q = externalSubmissionQueue(false)) == null) cancel = true; // terminating else q.push(task, this, false); @@ -3507,30 +3508,22 @@ public void run() { public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - DelayScheduler ds; DelayedTask t; - if (command == null || unit == null) - throw new NullPointerException(); - long d = (delay <= 0L) ? 0L : unit.toNanos(delay); - if (((ds = delayScheduler) == null && - (ds = startDelayScheduler()) == null) || - (runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - ds.pend(t = new DelayedTask(command, null, this, 0L, d)); + Objects.requireNonNull(command); + DelayedTask t = + new DelayedTask(command, null, this, false, 0L, + unit.toNanos(delay <= 0L ? 0L : delay)); + t.schedule(); return t; } public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - DelayScheduler ds; DelayedTask t; - if (callable == null || unit == null) - throw new NullPointerException(); - long d = (delay <= 0L) ? 0L : unit.toNanos(delay); - if (((ds = delayScheduler) == null && - (ds = startDelayScheduler()) == null) || - (runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - ds.pend(t = new DelayedTask(null, callable, this, 0L, d)); + Objects.requireNonNull(callable); + DelayedTask t = + new DelayedTask(null, callable, this, false, 0L, + unit.toNanos(delay <= 0L ? 0L : delay)); + t.schedule(); return t; } @@ -3538,18 +3531,15 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - DelayScheduler ds; DelayedTask t; - if (command == null || unit == null) - throw new NullPointerException(); + Objects.requireNonNull(command); if (period <= 0L) throw new IllegalArgumentException(); - long p = -unit.toNanos(period); // negative for fixed rate - long d = (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay); - if (((ds = delayScheduler) == null && - (ds = startDelayScheduler()) == null) || - (runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - ds.pend(t = new DelayedTask(command, null, this, p, d)); + DelayedTask t = + new DelayedTask(command, null, this, false, + -unit.toNanos(period), // negative for fixed rate + unit.toNanos(initialDelay <= 0L ? 0L : + initialDelay)); + t.schedule(); return t; } @@ -3557,18 +3547,15 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - DelayScheduler ds; DelayedTask t; - if (command == null || unit == null) - throw new NullPointerException(); + Objects.requireNonNull(command); if (delay <= 0L) throw new IllegalArgumentException(); - long p = unit.toNanos(delay); - long d = (initialDelay <= 0L) ? 0L : unit.toNanos(initialDelay); - if (((ds = delayScheduler) == null && - (ds = startDelayScheduler()) == null) || - (runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - ds.pend(t = new DelayedTask(command, null, this, p, d)); + DelayedTask t = + new DelayedTask(command, null, this, false, + unit.toNanos(delay), + unit.toNanos(initialDelay <= 0L ? 0L : + initialDelay)); + t.schedule(); return t; } @@ -3599,26 +3586,16 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, public ForkJoinTask submitWithTimeout(Callable callable, long timeout, TimeUnit unit) { - ForkJoinTask.CallableWithCanceller task; CancelAction onTimeout; - DelayScheduler ds; - if (callable == null || unit == null) - throw new NullPointerException(); - long d = (timeout <= 0L) ? 0L : unit.toNanos(timeout); - if (((ds = delayScheduler) == null && - (ds = startDelayScheduler()) == null) || - (runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - DelayedTask canceller = new DelayedTask( - onTimeout = new CancelAction(), null, this, 0L, d); - canceller.isImmediate = true; + ForkJoinTask.CallableWithCanceller task; + Objects.requireNonNull(callable); + long d = unit.toNanos(timeout <= 0L ? 0L : timeout); + CancelAction onTimeout = new CancelAction(); + DelayedTask canceller = + new DelayedTask(onTimeout, null, this, true, 0L, d); onTimeout.task = task = new ForkJoinTask.CallableWithCanceller(callable, canceller); - if (d == 0L) - task.trySetCancelled(); - else { - poolSubmit(true, task); - ds.pend(canceller); - } + canceller.schedule(); + poolSubmit(true, task); return task; } From a0db427ea956bc5c3843ff526b61bf2e6ed3527f Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sat, 8 Feb 2025 10:50:15 -0500 Subject: [PATCH 21/40] Isolate screening --- .../java/util/concurrent/DelayScheduler.java | 9 +++--- .../java/util/concurrent/ForkJoinPool.java | 28 ++++++++----------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index ccb5a8f6b501f..11d767b3f60e0 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -49,7 +49,7 @@ final class DelayScheduler extends Thread { * A DelayScheduler maintains a binary heap based on trigger times * (field DelayedTask.when) along with a pending queue of tasks * submitted by other threads. When ready, tasks are relayed to - * the pool. + * the pool (or run directly if in task.isImmediate). * * To reduce memory contention, the heap is maintained solely via * local variables in method loop() (forcing noticeable code @@ -90,7 +90,7 @@ final class DelayScheduler extends Thread { * To ensure that comparisons do not encounter integer wrap * errors, times are offset with the most negative possible value * (nanoTimeOffset) determined during static initialization. - * Negative delays must be screened out in public submission methods + * Negative delays are screened out before use. * * For the sake of compatibility with ScheduledThreadPoolExecutor, * shutdown follows the same rules, which add some further steps @@ -201,7 +201,6 @@ public final void run() { */ private void loop(ForkJoinPool p) { p.onDelaySchedulerStart(); - active = 1; DelayedTask[] h = new DelayedTask[INITIAL_HEAP_CAPACITY]; boolean purgedPeriodic = false; for (int n = 0;;) { // n is heap size @@ -401,16 +400,16 @@ static final class DelayedTask extends ForkJoinTask.InterruptibleTask public DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, boolean isImmediate, long nextDelay, long delay) { heapIndex = -1; + this.when = delay; // offset by now() on schedule() this.runnable = runnable; this.callable = callable; this.pool = pool; this.isImmediate = isImmediate; this.nextDelay = nextDelay; - this.when = delay; // offset by DelayScheduler.now on schedule() } public final void schedule() { - when += DelayScheduler.now(); + when = Math.max(0L, when) + DelayScheduler.now(); pool.scheduleDelayedTask(this); } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index dd21237defe19..5dc5d305f7d81 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3509,9 +3509,8 @@ public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { Objects.requireNonNull(command); - DelayedTask t = - new DelayedTask(command, null, this, false, 0L, - unit.toNanos(delay <= 0L ? 0L : delay)); + DelayedTask t = new DelayedTask( + command, null, this, false, 0L, unit.toNanos(delay)); t.schedule(); return t; } @@ -3520,9 +3519,8 @@ public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { Objects.requireNonNull(callable); - DelayedTask t = - new DelayedTask(null, callable, this, false, 0L, - unit.toNanos(delay <= 0L ? 0L : delay)); + DelayedTask t = new DelayedTask( + null, callable, this, false, 0L, unit.toNanos(delay)); t.schedule(); return t; } @@ -3534,11 +3532,9 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, Objects.requireNonNull(command); if (period <= 0L) throw new IllegalArgumentException(); - DelayedTask t = - new DelayedTask(command, null, this, false, - -unit.toNanos(period), // negative for fixed rate - unit.toNanos(initialDelay <= 0L ? 0L : - initialDelay)); + long p = -unit.toNanos(period); // negative for fixed rate + DelayedTask t = new DelayedTask( + command, null, this, false, p, unit.toNanos(initialDelay)); t.schedule(); return t; } @@ -3550,11 +3546,9 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, Objects.requireNonNull(command); if (delay <= 0L) throw new IllegalArgumentException(); - DelayedTask t = - new DelayedTask(command, null, this, false, - unit.toNanos(delay), - unit.toNanos(initialDelay <= 0L ? 0L : - initialDelay)); + long p = unit.toNanos(delay); + DelayedTask t = new DelayedTask( + command, null, this, false, p, unit.toNanos(initialDelay)); t.schedule(); return t; } @@ -3588,7 +3582,7 @@ public ForkJoinTask submitWithTimeout(Callable callable, TimeUnit unit) { ForkJoinTask.CallableWithCanceller task; Objects.requireNonNull(callable); - long d = unit.toNanos(timeout <= 0L ? 0L : timeout); + long d = unit.toNanos(timeout); CancelAction onTimeout = new CancelAction(); DelayedTask canceller = new DelayedTask(onTimeout, null, this, true, 0L, d); From d0f4af17905541a6166176118cfc6b22f29908a1 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 9 Feb 2025 10:57:58 -0500 Subject: [PATCH 22/40] Support STPE policy methods --- .../util/concurrent/CompletableFuture.java | 2 +- .../java/util/concurrent/DelayScheduler.java | 101 +++++++---- .../java/util/concurrent/ForkJoinPool.java | 158 +++++++++++++----- 3 files changed, 186 insertions(+), 75 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java index 91b1c581af958..619aac3615e6e 100644 --- a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java +++ b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java @@ -478,7 +478,7 @@ public static interface AsynchronousCompletionTask { private static final ForkJoinPool ASYNC_POOL = ForkJoinPool.asyncCommonPool(); - private static ScheduledFuture delay(Runnable command, long delay, + static ScheduledFuture delay(Runnable command, long delay, TimeUnit unit) { return ASYNC_POOL.schedule(command, delay, unit); } diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index 11d767b3f60e0..d4c2456bab91e 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -95,18 +95,20 @@ final class DelayScheduler extends Thread { * For the sake of compatibility with ScheduledThreadPoolExecutor, * shutdown follows the same rules, which add some further steps * beyond the cleanup associated with shutdownNow. Upon noticing - * pool shutdown, all periodic tasks are purged; 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). + * pool shutdown, delayed and/or periodic tasks are purged; 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). */ private static final int INITIAL_HEAP_CAPACITY = 1 << 6; private final ForkJoinPool pool; // read only once int restingSize; // written only before parking private volatile int active; // 0: inactive, -1: stopped, +1: running + private volatile int cancelDelayedOnShutdown; + private volatile int preservePeriodicOnShutdown; @jdk.internal.vm.annotation.Contended() private volatile DelayedTask pending; @@ -186,7 +188,7 @@ public final void run() { } finally { restingSize = 0; active = -1; - ForkJoinPool.poolCanTerminate(p); + p.tryTerminateFromDelayScheduler(); } } } @@ -202,9 +204,8 @@ public final void run() { private void loop(ForkJoinPool p) { p.onDelaySchedulerStart(); DelayedTask[] h = new DelayedTask[INITIAL_HEAP_CAPACITY]; - boolean purgedPeriodic = false; for (int n = 0;;) { // n is heap size - DelayedTask t; + DelayedTask t; int runStatus; while (pending != null && // process pending tasks (t = (DelayedTask) U.getAndSetReference(this, PENDING, null)) != null) { @@ -247,11 +248,9 @@ else if (t.status >= 0) { } while ((t = next) != null); } - if (ForkJoinPool.poolIsShutdown(p)) { - if ((n = tryStop(p, h, n, purgedPeriodic)) < 0) - break; - purgedPeriodic = true; - } + if ((runStatus = p.delaySchedulerRunStatus()) != 0 && + (n = tryStop(p, h, n, runStatus)) < 0) + break; long parkTime = 0L; // zero for untimed park if (n > 0 && h.length > 0) { @@ -343,20 +342,26 @@ private static int replace(DelayedTask[] h, int k, int n) { /** * Call only when pool is shutdown. If called when not stopping, - * removes periodic tasks if not already done so, and if not empty - * or pool not terminating, returns. Otherwise, cancels all tasks - * in heap and pending queue. + * removes tasks according to policy 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, DelayedTask[] h, int n, - boolean purgedPeriodic) { - if (p != null && h != null && h.length >= n) { - if (!ForkJoinPool.poolIsStopping(p)) { - if (!purgedPeriodic && n > 0) { - DelayedTask t; int stat; // remove periodic tasks + int runStatus) { + if (runStatus > 0 && p != null) { + if (n > 0) { + boolean cancelPeriodic = (preservePeriodicOnShutdown == 0); + boolean cancelDelayed = (cancelDelayedOnShutdown != 0); + if (cancelDelayed && cancelPeriodic) + n = cancelAll(h, n); + else if (h != null && h.length >= n) { + DelayedTask t; int stat; for (int i = n - 1; i >= 0; --i) { if ((t = h[i]) != null && - ((stat = t.status) < 0 || t.nextDelay != 0L)) { + ((stat = t.status) < 0 || + ((t.nextDelay == 0L) ? + cancelDelayed : cancelPeriodic))) { t.heapIndex = -1; if (stat >= 0) t.trySetCancelled(); @@ -364,21 +369,47 @@ private int tryStop(ForkJoinPool p, DelayedTask[] h, int n, } } } - if (n > 0 || !ForkJoinPool.poolCanTerminate(p)) - return n; } + if (n > 0 || !p.tryTerminateFromDelayScheduler()) + return n; + } + if (n > 0) + cancelAll(h, n); + for (DelayedTask a = (DelayedTask) + U.getAndSetReference(this, PENDING, null); + a != null; a = a.nextPending) + a.trySetCancelled(); // clear pending requests + return -1; + } + + private int cancelAll(DelayedTask[] h, int n) { + if (h != null && h.length >= n) { + DelayedTask t; for (int i = 0; i < n; ++i) { - DelayedTask u = h[i]; - h[i] = null; - if (u != null) - u.trySetCancelled(); + if ((t = h[i]) != null) { + h[i] = null; + t.heapIndex = -1; + t.trySetCancelled(); + } } - for (DelayedTask a = (DelayedTask) - U.getAndSetReference(this, PENDING, null); - a != null; a = a.nextPending) - a.trySetCancelled(); // clear pending requests } - return -1; + return 0; + } + + // policy methods + void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) { + preservePeriodicOnShutdown = (value ? 1 : 0); + ensureActive(); + } + boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() { + return (preservePeriodicOnShutdown != 0); + } + void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) { + cancelDelayedOnShutdown = (value ? 0 : 1); + ensureActive(); + } + boolean getExecuteExistingDelayedTasksAfterShutdownPolicy() { + return (cancelDelayedOnShutdown == 0); } /** @@ -431,7 +462,7 @@ final boolean postExec() { // resubmit if periodic long d; ForkJoinPool p; DelayScheduler ds; if ((d = nextDelay) != 0L && status >= 0 && (p = pool) != null && (ds = p.delayScheduler) != null) { - if (!ForkJoinPool.poolIsShutdown(p)) { + if (p.delaySchedulerRunStatus() == 0) { heapIndex = -1; if (d < 0L) when = DelayScheduler.now() - d; diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 5dc5d305f7d81..eb97019eee42e 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -138,20 +138,22 @@ * *

    Additionally, this class supports {@link * ScheduledExecutorService} methods to delay or periodically execute - * tasks, as well as method {#link #submitWithTimeout} to - * cancel tasks that take too long. The scheduled functions or actions - * may create and invoke other {@linkplain ForkJoinTask - * ForkJoinTasks}. Resource exhaustion encountered after initial - * submission results in task cancellation. When time-based methods - * are used, shutdown policies are based on the default policies of - * class {@link ScheduledThreadPoolExecutor}: upon {@link #shutdown}, - * existing periodic tasks will not re-execute, and the pool - * terminates when quiescent and existing delayed tasks - * complete. Method {@link #shutdownNow} may be used to instead - * unconditionally initiate pool termination. Monitoring methods such - * as {@link getQueuedTaskCount} do not include scheduled tasks that - * are not yet ready to execute, whcih are reported separately by - * method {@link getDelayedTaskCount}. + * tasks, as well as method {#link #submitWithTimeout} to cancel tasks + * that take too long. The scheduled functions or actions may create + * and invoke other {@linkplain ForkJoinTask ForkJoinTasks}. Resource + * exhaustion encountered after initial submission results in task + * cancellation. When time-based methods are used, shutdown policies + * are based on the default policies of class {@link + * ScheduledThreadPoolExecutor}: upon {@link #shutdown}, existing + * periodic tasks will not re-execute, and the pool terminates when + * quiescent and existing delayed tasks complete. Methods {@link + * #setContinueExistingPeriodicTasksAfterShutdownPolicy} and {@link + * #setExecuteExistingDelayedTasksAfterShutdownPolicy} may be used to + * alter these policies, and method {@link #shutdownNow} may be used + * to instead unconditionally initiate pool termination. Monitoring + * methods such as {@link getQueuedTaskCount} do not include scheduled + * tasks that are not yet ready to execute, whcih are reported + * separately by method {@link getDelayedTaskCount}. * *

    The parameters used to construct the common pool may be controlled by * setting the following {@linkplain System#getProperty system properties}: @@ -910,7 +912,7 @@ public class ForkJoinPool extends AbstractExecutorService * * This class supports ScheduledExecutorService methods by * creating and starting a DelayScheduler on first use of these - * methods (via scheduleDelayedTask, with callback + * methods (via startDelayScheduler, with callback * onDelaySchedulerStart). The scheduler operates independently in * its own thread, relaying tasks to the pool to execute as they * become ready (see method executeReadyDelayedTask). The only @@ -921,10 +923,7 @@ public class ForkJoinPool extends AbstractExecutorService * which tasks are submitted before shutdown, but not ready until * afterwards, in which case they must bypass some screening to be * allowed to run. Conversely, the DelayScheduler interacts with - * the pool only to check runState status (via mehods - * poolIsStopping and poolIsShutdown) and complete termination - * (via poolCanTerminate) that invoke corresponding private pool - * implementations. + * the pool only to check runState status and complete termination. * * Memory placement * ================ @@ -1730,6 +1729,10 @@ private long spinLockRunState() { // spin/sleep } } + static boolean poolIsStopping(ForkJoinPool p) { // Used by ForkJoinTask + return p != null && (p.runState & STOP) != 0L; + } + // Creating, registering, and deregistering workers /** @@ -3414,16 +3417,8 @@ public T invokeAny(Collection> tasks, // Support for delayed tasks - // status methods used by Delayscheduler and other classes in this package - static boolean poolIsStopping(ForkJoinPool p) { - return p != null && (p.runState & STOP) != 0L; - } - static boolean poolIsShutdown(ForkJoinPool p) { - return p != null && (p.runState & SHUTDOWN) != 0L; - } - static boolean poolCanTerminate(ForkJoinPool p) { // true if terminated - return p != null && (p.tryTerminate(false, false) & STOP) != 0L; - } + // methods used by Delayscheduler + static ForkJoinPool asyncCommonPool() { // override parallelism == 0 ForkJoinPool cp; if ((cp = common).parallelism == 0) @@ -3431,11 +3426,17 @@ static ForkJoinPool asyncCommonPool() { // override parallelism == 0 return cp; } - /** - * Arrange delayed execution of a DelayedTask via the - * DelayScheduler, creating and starting it if necessary. - */ - final void scheduleDelayedTask(DelayedTask task) { + final int delaySchedulerRunStatus() { // <0:stop, >0:shutdown, else 0 + long rs; + return ((((rs = runState) & SHUTDOWN) == 0L) ? 0 : + (rs & STOP) != 0L ? -1 : 1); + } + + final boolean tryTerminateFromDelayScheduler() { + return (tryTerminate(false, false) & STOP) != 0L; + } + + private DelayScheduler startDelayScheduler() { DelayScheduler ds; if ((ds = delayScheduler) == null) { boolean start = false; @@ -3459,9 +3460,7 @@ final void scheduleDelayedTask(DelayedTask task) { ds.start(); } } - if (ds == null || task == null || (runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - ds.pend(task); + return ds; } /** @@ -3473,6 +3472,19 @@ final void onDelaySchedulerStart() { q.unlockPhase(); } + /** + * Arrange delayed execution of a DelayedTask via the + * DelayScheduler, creating and starting it if necessary. + */ + final void scheduleDelayedTask(DelayedTask task) { + DelayScheduler ds; + if (((ds = delayScheduler) == null && + (ds = startDelayScheduler()) == null) || + task == null || (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + ds.pend(task); + } + /** * Arranges execution of a ready task from DelayScheduler */ @@ -3497,9 +3509,9 @@ final void executeReadyDelayedTask(DelayedTask task) { * Body of a DelayedTask serving to cancel another task on timeout */ static final class CancelAction implements Runnable { - ForkJoinTask task; // set after construction + Future task; // set after construction public void run() { - ForkJoinTask t; + Future t; if ((t = task) != null) t.cancel(true); } @@ -3593,6 +3605,74 @@ public ForkJoinTask submitWithTimeout(Callable callable, return task; } + /** + * 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) { + DelayScheduler ds; + if ((ds = delayScheduler) != null || (ds = startDelayScheduler()) != null) + ds.setContinueExistingPeriodicTasksAfterShutdownPolicy(value); + } + + /** + * 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() { + DelayScheduler ds; + if ((ds = delayScheduler) != null || (ds = startDelayScheduler()) != null) + return ds.getContinueExistingPeriodicTasksAfterShutdownPolicy(); + return false; + } + + /** + * 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) { + DelayScheduler ds; + if ((ds = delayScheduler) != null || (ds = startDelayScheduler()) != null) + ds.setExecuteExistingDelayedTasksAfterShutdownPolicy(value); + } + + /** + * 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() { + DelayScheduler ds; + if ((ds = delayScheduler) != null || (ds = startDelayScheduler()) != null) + return ds.getExecuteExistingDelayedTasksAfterShutdownPolicy(); + return true; + } + /** * Returns the factory used for constructing new workers. * From a6290ab74e885f804b4ba351515578bcf873eabe Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 9 Feb 2025 18:01:35 -0500 Subject: [PATCH 23/40] Reduce memory accesses --- .../java/util/concurrent/DelayScheduler.java | 105 ++++++++++-------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index d4c2456bab91e..2f7ec8bb72381 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -79,13 +79,11 @@ final class DelayScheduler extends Thread { * IO-based timeouts). Cancellations are added to the pending * queue in method DelayedTask.cancel(), to remove them from the * heap. (This requires some safeguards to deal with tasks - * cancelled while they are still pending.) In addition, - * cancelled tasks set their "when" fields to Long.MAX_VALUE, - * which causes them to be pushed toward the bottom of the heap - * where they can be simply swept out in the course of other add - * and replace operations, even before processing the removal - * request (which is then a no-op). In the mean time, these - * elements might harmlessly be out of numerical heap order. + * cancelled while they are still pending.) In addition, the heap + * replace method removes any cancelled tasks seen while + * performing sift-down operations, in which case elements are + * removed even before processing the removal request (which is + * then a no-op). * * To ensure that comparisons do not encounter integer wrap * errors, times are offset with the most negative possible value @@ -105,12 +103,11 @@ final class DelayScheduler extends Thread { private static final int INITIAL_HEAP_CAPACITY = 1 << 6; private final ForkJoinPool pool; // read only once - int restingSize; // written only before parking + private volatile DelayedTask pending; // for submited adds and removes private volatile int active; // 0: inactive, -1: stopped, +1: running private volatile int cancelDelayedOnShutdown; private volatile int preservePeriodicOnShutdown; - @jdk.internal.vm.annotation.Contended() - private volatile DelayedTask pending; + int restingSize; // written only before parking private static final Unsafe U; private static final long ACTIVE; @@ -212,25 +209,21 @@ private void loop(ForkJoinPool p) { DelayedTask next; int cap = h.length; do { - int i = t.heapIndex; + next = t.nextPending; long d = t.when; - if ((next = t.nextPending) != null) + int i = t.heapIndex, stat = t.status; + if (next != null) t.nextPending = null; if (i >= 0) { t.heapIndex = -1; if (i < n && i < cap && h[i] == t) n = replace(h, i, n); } - else if (t.status >= 0) { - DelayedTask parent, u; int pk; DelayedTask[] nh; + else if (stat >= 0) { + DelayedTask parent; int pk; DelayedTask[] nh; if (n >= cap || n < 0) // couldn't resize t.trySetCancelled(); else { - while (n > 0 && // clear trailing cancellations - (u = h[n - 1]) != null && u.status < 0) { - u.heapIndex = -1; - h[--n] = null; - } int k = n++; while (k > 0 && // sift up (parent = h[pk = (k - 1) >>> 1]) != null && @@ -301,41 +294,57 @@ private DelayedTask[] growHeap(DelayedTask[] h, int cap) { } /** - * Replaces removed heap element at index k + * Replaces removed heap element at index k, along with other + * cancelled nodes found while doing so. * @return current heap size */ private static int replace(DelayedTask[] h, int k, int n) { - if (k >= 0 && n > 0 && h != null && n <= h.length) { - DelayedTask 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; + if (h != null && n <= h.length) { + while (k >= 0 && k < n) { + int alsoReplace = -1; // nonnegative if cancelled task seen + DelayedTask 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; } - u.heapIndex = -1; } - } - if (t != null) { - int ck, rk; DelayedTask c, r; - while ((ck = (k << 1) + 1) < n && (c = h[ck]) != null) { - long cd = c.when, rd; - if ((rk = ck + 1) < n && (r = h[rk]) != null && - (rd = r.when) < cd) { - c = r; ck = rk; cd = rd; // use right child + if (t != null) { // sift down + int ck, rk; long cd, rd; DelayedTask c, r; + while ((ck = (k << 1) + 1) < n && (c = h[ck]) != null) { + cd = c.when; + if (c.status < 0 && alsoReplace < 0) { + alsoReplace = ck; // at most one per pass + c.heapIndex = -1; + cd = Long.MAX_VALUE; // prevent swap below + } + if ((rk = ck + 1) < n && (r = h[rk]) != null) { + rd = r.when; + if (r.status < 0 && alsoReplace < 0) { + alsoReplace = rk; + r.heapIndex = -1; + } + else if (rd < cd) { // use right child + cd = rd; c = r; ck = rk; + } + } + if (d <= cd) + break; + c.heapIndex = k; + h[k] = c; + k = ck; } - if (d <= cd) - break; - c.heapIndex = k; - h[k] = c; - k = ck; + t.heapIndex = k; } - t.heapIndex = k; + h[k] = t; + k = alsoReplace; } - h[k] = t; } return n; } @@ -492,10 +501,8 @@ public final boolean cancel(boolean mayInterruptIfRunning) { } } else if (heapIndex >= 0 && nextPending == null && - (p = pool) != null && (ds = p.delayScheduler) != null) { - when = Long.MAX_VALUE; // set to max delay + (p = pool) != null && (ds = p.delayScheduler) != null) ds.pend(this); // for heap cleanup - } } return isCancelled; } From c8392995c0fcf8dd9dcd85c6da59cbb7cf83dc00 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Mon, 10 Feb 2025 08:25:31 -0500 Subject: [PATCH 24/40] Simplify policy methods; improve layout --- .../java/util/concurrent/DelayScheduler.java | 45 ++----- .../java/util/concurrent/ForkJoinPool.java | 115 ++++++------------ 2 files changed, 50 insertions(+), 110 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index 2f7ec8bb72381..5ac96c2dc1f64 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -43,6 +43,7 @@ * An add-on for ForkJoinPools that provides scheduling for * delayed and periodic tasks */ +@jdk.internal.vm.annotation.Contended() final class DelayScheduler extends Thread { /* @@ -102,11 +103,9 @@ final class DelayScheduler extends Thread { */ private static final int INITIAL_HEAP_CAPACITY = 1 << 6; - private final ForkJoinPool pool; // read only once - private volatile DelayedTask pending; // for submited adds and removes - private volatile int active; // 0: inactive, -1: stopped, +1: running - private volatile int cancelDelayedOnShutdown; - private volatile int preservePeriodicOnShutdown; + private ForkJoinPool pool; // read once and detached upon starting + volatile DelayedTask pending; // for submited adds and removes + volatile int active; // 0: inactive, -1: stopped, +1: running int restingSize; // written only before parking private static final Unsafe U; @@ -178,14 +177,15 @@ final int approximateSize() { * Sets up and runs scheduling loop */ public final void run() { - ForkJoinPool p; - if ((p = pool) != null) { + ForkJoinPool p = pool; + pool = null; // detach + if (p != null) { try { loop(p); } finally { restingSize = 0; active = -1; - p.tryTerminateFromDelayScheduler(); + p.tryStopIfEnabled(); } } } @@ -360,17 +360,13 @@ private int tryStop(ForkJoinPool p, DelayedTask[] h, int n, int runStatus) { if (runStatus > 0 && p != null) { if (n > 0) { - boolean cancelPeriodic = (preservePeriodicOnShutdown == 0); - boolean cancelDelayed = (cancelDelayedOnShutdown != 0); - if (cancelDelayed && cancelPeriodic) + if (runStatus > 1) n = cancelAll(h, n); else if (h != null && h.length >= n) { DelayedTask t; int stat; for (int i = n - 1; i >= 0; --i) { if ((t = h[i]) != null && - ((stat = t.status) < 0 || - ((t.nextDelay == 0L) ? - cancelDelayed : cancelPeriodic))) { + ((stat = t.status) < 0 || t.nextDelay != 0L)) { t.heapIndex = -1; if (stat >= 0) t.trySetCancelled(); @@ -379,7 +375,7 @@ else if (h != null && h.length >= n) { } } } - if (n > 0 || !p.tryTerminateFromDelayScheduler()) + if (n > 0 || !p.tryStopIfEnabled()) return n; } if (n > 0) @@ -405,22 +401,6 @@ private int cancelAll(DelayedTask[] h, int n) { return 0; } - // policy methods - void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) { - preservePeriodicOnShutdown = (value ? 1 : 0); - ensureActive(); - } - boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() { - return (preservePeriodicOnShutdown != 0); - } - void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) { - cancelDelayedOnShutdown = (value ? 0 : 1); - ensureActive(); - } - boolean getExecuteExistingDelayedTasksAfterShutdownPolicy() { - return (cancelDelayedOnShutdown == 0); - } - /** * Task class for DelayScheduler operations */ @@ -449,8 +429,7 @@ public DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, } public final void schedule() { - when = Math.max(0L, when) + DelayScheduler.now(); - pool.scheduleDelayedTask(this); + pool.scheduleDelayedTask(this, when); } public final T getRawResult() { return result; } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index eb97019eee42e..c31dec0a81b3d 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -146,11 +146,10 @@ * are based on the default policies of class {@link * ScheduledThreadPoolExecutor}: upon {@link #shutdown}, existing * periodic tasks will not re-execute, and the pool terminates when - * quiescent and existing delayed tasks complete. Methods {@link - * #setContinueExistingPeriodicTasksAfterShutdownPolicy} and {@link - * #setExecuteExistingDelayedTasksAfterShutdownPolicy} may be used to - * alter these policies, and method {@link #shutdownNow} may be used - * to instead unconditionally initiate pool termination. Monitoring + * quiescent and existing delayed tasks complete. Method {@link + * #cancelDelayedTasksOnShutdown} may be used to disable all delayed + * tasks upon shutdown, and method {@link #shutdownNow} may be used to + * instead unconditionally initiate pool termination. Monitoring * methods such as {@link getQueuedTaskCount} do not include scheduled * tasks that are not yet ready to execute, whcih are reported * separately by method {@link getDelayedTaskCount}. @@ -1650,6 +1649,8 @@ final boolean isApparentlyUnblocked() { final long config; // static configuration bits volatile long stealCount; // collects worker nsteals volatile long threadIds; // for worker thread names + volatile boolean cancelDelayedTasksOnShutdown; + @jdk.internal.vm.annotation.Contended("fjpctl") // segregate volatile long ctl; // main pool control @jdk.internal.vm.annotation.Contended("fjpctl") // colocate @@ -1856,8 +1857,7 @@ final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) { unlockRunState(); } } - if ((tryTerminate(false, false) & STOP) == 0L && - phase != 0 && w != null && w.source != DROPPED) { + if (!tryStopIfEnabled() && phase != 0 && w != null && w.source != DROPPED) { signalWork(); // possibly replace w.cancelTasks(); // clean queue } @@ -2793,6 +2793,13 @@ else if (quiet == 0 && (ds = delayScheduler) != null) return e; } + /** + * Tries to stop and possibly terminate if already enabled, return success. + */ + final boolean tryStopIfEnabled() { + return (tryTerminate(false, false) & STOP) != 0L; + } + /** * Scans queues in a psuedorandom order based on thread id, * cancelling tasks until empty, or returning early upon @@ -3426,14 +3433,18 @@ static ForkJoinPool asyncCommonPool() { // override parallelism == 0 return cp; } - final int delaySchedulerRunStatus() { // <0:stop, >0:shutdown, else 0 + /** + * Returns status code for DelayScheduler: + * * 0 not shutdown + * * -1 stopping + * * 1 shutdown without cancelling delayed tasks + * * 2 shutdown cancelling delayed tasks + */ + final int delaySchedulerRunStatus() { long rs; return ((((rs = runState) & SHUTDOWN) == 0L) ? 0 : - (rs & STOP) != 0L ? -1 : 1); - } - - final boolean tryTerminateFromDelayScheduler() { - return (tryTerminate(false, false) & STOP) != 0L; + (rs & STOP) != 0L ? -1 : + cancelDelayedTasksOnShutdown ? 2 : 1); } private DelayScheduler startDelayScheduler() { @@ -3476,12 +3487,13 @@ final void onDelaySchedulerStart() { * Arrange delayed execution of a DelayedTask via the * DelayScheduler, creating and starting it if necessary. */ - final void scheduleDelayedTask(DelayedTask task) { + final void scheduleDelayedTask(DelayedTask task, long nanoDelay) { DelayScheduler ds; if (((ds = delayScheduler) == null && (ds = startDelayScheduler()) == null) || task == null || (runState & SHUTDOWN) != 0L) throw new RejectedExecutionException(); + task.when = DelayScheduler.now() + Math.max(0L, nanoDelay); ds.pend(task); } @@ -3606,71 +3618,20 @@ public ForkJoinTask submitWithTimeout(Callable callable, } /** - * 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 + * Arranges that scheduled tasks that are not executing and have + * not already been enabled for execution are not executed and + * will be cancelled upon {@link #shutdown}. This method may be + * invoked either before {@link #shutdown} to take effect upon the + * next call, or afterwards, to cancel such tasks, that may allow + * termination. Note that the next executions of periodic tasks + * are always disabled upon shutdown, so this method applies + * meaningfully only to non-periodic tasks. */ - public void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) { + public void cancelDelayedTasksOnShutdown() { DelayScheduler ds; - if ((ds = delayScheduler) != null || (ds = startDelayScheduler()) != null) - ds.setContinueExistingPeriodicTasksAfterShutdownPolicy(value); - } - - /** - * 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() { - DelayScheduler ds; - if ((ds = delayScheduler) != null || (ds = startDelayScheduler()) != null) - return ds.getContinueExistingPeriodicTasksAfterShutdownPolicy(); - return false; - } - - /** - * 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) { - DelayScheduler ds; - if ((ds = delayScheduler) != null || (ds = startDelayScheduler()) != null) - ds.setExecuteExistingDelayedTasksAfterShutdownPolicy(value); - } - - /** - * 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() { - DelayScheduler ds; - if ((ds = delayScheduler) != null || (ds = startDelayScheduler()) != null) - return ds.getExecuteExistingDelayedTasksAfterShutdownPolicy(); - return true; + cancelDelayedTasksOnShutdown = true; + if ((ds = delayScheduler) != null) + ds.ensureActive(); } /** From f1394c48b2aef80702bb41af3f0e32fc13904262 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Wed, 12 Feb 2025 08:35:30 -0500 Subject: [PATCH 25/40] Rename DelayedTask to ScheduledForkJoinTask; misc other improvements --- .../java/util/concurrent/DelayScheduler.java | 194 ++++++++++-------- .../java/util/concurrent/ForkJoinPool.java | 166 ++++++++------- 2 files changed, 188 insertions(+), 172 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index 5ac96c2dc1f64..e04c055a68a0e 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -48,9 +48,9 @@ final class DelayScheduler extends Thread { /* * A DelayScheduler maintains a binary heap based on trigger times - * (field DelayedTask.when) along with a pending queue of tasks - * submitted by other threads. When ready, tasks are relayed to - * the pool (or run directly if in task.isImmediate). + * (field ScheduledForkJoinTask.when) along with a pending queue + * of tasks submitted by other threads. When ready, tasks are + * relayed to the pool (or run directly if in task.isImmediate). * * To reduce memory contention, the heap is maintained solely via * local variables in method loop() (forcing noticeable code @@ -69,19 +69,19 @@ final class DelayScheduler extends Thread { * 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 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 tryTerminate checks as - * well as to suppress reactivation while terminating. + * 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 tryTerminate + * checks as well as to suppress reactivation while terminating. * * The implementation is designed to accommodate usages in which * many or even most tasks are cancelled before executing (mainly * IO-based timeouts). Cancellations are added to the pending - * queue in method DelayedTask.cancel(), to remove them from the - * heap. (This requires some safeguards to deal with tasks - * cancelled while they are still pending.) In addition, the heap - * replace method removes any cancelled tasks seen while + * queue in method ScheduledForkJoinTask.cancel(), to remove them + * from the heap. (This requires some safeguards to deal with + * tasks cancelled while they are still pending.) In addition, + * the heap replace method removes any cancelled tasks seen while * performing sift-down operations, in which case elements are * removed even before processing the removal request (which is * then a no-op). @@ -104,7 +104,7 @@ final class DelayScheduler extends Thread { private static final int INITIAL_HEAP_CAPACITY = 1 << 6; private ForkJoinPool pool; // read once and detached upon starting - volatile DelayedTask pending; // for submited adds and removes + volatile ScheduledForkJoinTask pending; // for submited adds and removes volatile int active; // 0: inactive, -1: stopped, +1: running int restingSize; // written only before parking @@ -118,7 +118,7 @@ final class DelayScheduler extends Thread { ACTIVE = U.objectFieldOffset(klass, "active"); PENDING = U.objectFieldOffset(klass, "pending"); long ns = System.nanoTime(); // ensure negative to avoid overflow - nanoTimeOffset = Long.MIN_VALUE + (ns < 0L ? ns : 0L); + nanoTimeOffset = Long.MIN_VALUE + Math.min(ns, 0L); } DelayScheduler(ForkJoinPool p, String name) { @@ -135,7 +135,8 @@ static final long now() { } /** - * Ensure active, unparking if necessary, unless stopped + * Ensure active, unparking if necessary, unless stopped. + * Returns the status as observed prior to activating */ final int ensureActive() { int state; @@ -148,11 +149,11 @@ final int ensureActive() { * Inserts the task to pending queue, to add, remove, or ignore * depending on task status when processed. */ - final void pend(DelayedTask task) { - DelayedTask f = pending; + final void pend(ScheduledForkJoinTask task) { + ScheduledForkJoinTask f = pending; if (task != null) { do {} while ( - f != (f = (DelayedTask) + f != (f = (ScheduledForkJoinTask) U.compareAndExchangeReference( this, PENDING, task.nextPending = f, task))); ensureActive(); @@ -178,8 +179,8 @@ final int approximateSize() { */ public final void run() { ForkJoinPool p = pool; - pool = null; // detach - if (p != null) { + pool = null; // detach + if (p != null) { // currently always true try { loop(p); } finally { @@ -200,14 +201,15 @@ public final void run() { */ private void loop(ForkJoinPool p) { p.onDelaySchedulerStart(); - DelayedTask[] h = new DelayedTask[INITIAL_HEAP_CAPACITY]; - for (int n = 0;;) { // n is heap size - DelayedTask t; int runStatus; + ScheduledForkJoinTask[] h = // initial heap array + new ScheduledForkJoinTask[INITIAL_HEAP_CAPACITY]; + int cap = h.length, n = 0, prevRunStatus = 0; // n is heap size + for (;;) { // loop until stopped + ScheduledForkJoinTask t; int runStatus; while (pending != null && // process pending tasks - (t = (DelayedTask) + (t = (ScheduledForkJoinTask) U.getAndSetReference(this, PENDING, null)) != null) { - DelayedTask next; - int cap = h.length; + ScheduledForkJoinTask next; do { next = t.nextPending; long d = t.when; @@ -220,12 +222,13 @@ private void loop(ForkJoinPool p) { n = replace(h, i, n); } else if (stat >= 0) { - DelayedTask parent; int pk; DelayedTask[] nh; if (n >= cap || n < 0) // couldn't resize t.trySetCancelled(); - else { - int k = n++; - while (k > 0 && // sift up + else { // add and sift up + ScheduledForkJoinTask parent; + ScheduledForkJoinTask[] nh; + int k = n++, pk, nc; + while (k > 0 && (parent = h[pk = (k - 1) >>> 1]) != null && (parent.when > d)) { parent.heapIndex = k; @@ -234,36 +237,40 @@ else if (stat >= 0) { } t.heapIndex = k; h[k] = t; - if (n >= cap && (nh = growHeap(h, cap)) != null) - cap = (h = nh).length; + if (n >= cap && (nh = growHeap(h, cap)) != null && + (nc = nh.length) > cap) { + cap = nc; // else keep using old array + h = nh; + } } } } while ((t = next) != null); } - if ((runStatus = p.delaySchedulerRunStatus()) != 0 && - (n = tryStop(p, h, n, runStatus)) < 0) - break; + if ((runStatus = p.delaySchedulerRunStatus()) != 0) { + if ((n = tryStop(p, h, n, runStatus, prevRunStatus)) < 0) + break; + prevRunStatus = runStatus; + } long parkTime = 0L; // zero for untimed park - if (n > 0 && h.length > 0) { - do { // submit ready tasks - DelayedTask 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) { - if (f.isImmediate) - f.doExec(); - else - p.executeReadyDelayedTask(f); - } + while (n > 0 && h.length > 0) { // submit ready tasks + 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) { + if (f.isImmediate) + f.doExec(); + else + p.executeReadyScheduledTask(f); } - } while ((n = replace(h, 0, n)) > 0); + } + n = replace(h, 0, n); } if (pending == null) { @@ -278,12 +285,12 @@ else if (stat >= 0) { } /** - * Tries to reallocate the heap array, returning existing - * array on failure. + * Tries to reallocate the heap array; returning null on failure */ - private DelayedTask[] growHeap(DelayedTask[] h, int cap) { - int newCap = cap << 1; - DelayedTask[] nh = h; + private ScheduledForkJoinTask[] growHeap(ScheduledForkJoinTask[] h, + int cap) { + int newCap = cap << 1; + ScheduledForkJoinTask[] nh = null; if (h != null && h.length == cap && cap < newCap) { try { nh = Arrays.copyOf(h, newCap); @@ -298,11 +305,11 @@ private DelayedTask[] growHeap(DelayedTask[] h, int cap) { * cancelled nodes found while doing so. * @return current heap size */ - private static int replace(DelayedTask[] h, int k, int n) { - if (h != null && n <= h.length) { - while (k >= 0 && k < n) { - int alsoReplace = -1; // nonnegative if cancelled task seen - DelayedTask t = null, u; + private static int replace(ScheduledForkJoinTask[] h, int k, int n) { + if (h != null && h.length >= n) { + 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) { @@ -316,8 +323,9 @@ private static int replace(DelayedTask[] h, int k, int n) { } } if (t != null) { // sift down - int ck, rk; long cd, rd; DelayedTask c, r; - while ((ck = (k << 1) + 1) < n && (c = h[ck]) != null) { + int ck, rk; long cd, rd; ScheduledForkJoinTask c, r; + while ((ck = (k << 1) + 1) < n && ck >= 0 && + (c = h[ck]) != null) { cd = c.when; if (c.status < 0 && alsoReplace < 0) { alsoReplace = ck; // at most one per pass @@ -351,19 +359,19 @@ else if (rd < cd) { // use right child /** * Call only when pool is shutdown. If called when not stopping, - * removes tasks according to policy not already done so, and if - * not empty or pool not terminating, returns. Otherwise, cancels - * all tasks in heap and pending queue. + * 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, DelayedTask[] h, int n, - int runStatus) { - if (runStatus > 0 && p != null) { + private int tryStop(ForkJoinPool p, ScheduledForkJoinTask[] h, int n, + int runStatus, int prevRunStatus) { + if (runStatus > 0) { if (n > 0) { if (runStatus > 1) n = cancelAll(h, n); - else if (h != null && h.length >= n) { - DelayedTask t; int stat; + else if (runStatus != prevRunStatus && 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)) { @@ -375,21 +383,21 @@ else if (h != null && h.length >= n) { } } } - if (n > 0 || !p.tryStopIfEnabled()) + if (n > 0 || p == null || !p.tryStopIfEnabled()) return n; } if (n > 0) cancelAll(h, n); - for (DelayedTask a = (DelayedTask) + for (ScheduledForkJoinTask a = (ScheduledForkJoinTask) U.getAndSetReference(this, PENDING, null); a != null; a = a.nextPending) a.trySetCancelled(); // clear pending requests return -1; } - private int cancelAll(DelayedTask[] h, int n) { + private int cancelAll(ScheduledForkJoinTask[] h, int n) { if (h != null && h.length >= n) { - DelayedTask t; + ScheduledForkJoinTask t; for (int i = 0; i < n; ++i) { if ((t = h[i]) != null) { h[i] = null; @@ -405,37 +413,43 @@ private int cancelAll(DelayedTask[] h, int n) { * Task class for DelayScheduler operations */ @SuppressWarnings("serial") - static final class DelayedTask extends ForkJoinTask.InterruptibleTask + static final class ScheduledForkJoinTask + extends ForkJoinTask.InterruptibleTask implements ScheduledFuture { - final Runnable runnable; // only one of runnable or callable nonnull + final Runnable runnable; // only one of runnable or callable nonnull final Callable callable; final ForkJoinPool pool; T result; - DelayedTask nextPending; // for DelayScheduler submissions - final long nextDelay; // 0: once; <0: fixedDelay; >0: fixedRate + ScheduledForkJoinTask nextPending; // for DelayScheduler submissions 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 - public DelayedTask(Runnable runnable, Callable callable, ForkJoinPool pool, - boolean isImmediate, long nextDelay, long delay) { + public ScheduledForkJoinTask(long when, long nextDelay, boolean isImmediate, + Runnable runnable, Callable callable, + ForkJoinPool pool) { heapIndex = -1; - this.when = delay; // offset by now() on schedule() + this.when = when; + this.isImmediate = isImmediate; + this.nextDelay = nextDelay; this.runnable = runnable; this.callable = callable; this.pool = pool; - this.isImmediate = isImmediate; - this.nextDelay = nextDelay; } - public final void schedule() { - pool.scheduleDelayedTask(this, when); + public void schedule() { // relay to pool, to allow independent use + pool.scheduleDelayedTask(this); } + @Override public final T getRawResult() { return result; } + @Override public final void setRawResult(T v) { result = v; } + @Override final Object adaptee() { return (runnable != null) ? runnable : callable; } + @Override final T compute() throws Exception { Callable c; Runnable r; T res = null; @@ -446,6 +460,7 @@ else if ((c = callable) != null) return res; } + @Override final boolean postExec() { // resubmit if periodic long d; ForkJoinPool p; DelayScheduler ds; if ((d = nextDelay) != 0L && status >= 0 && @@ -464,6 +479,7 @@ final boolean postExec() { // resubmit if periodic return true; } + @Override public final boolean cancel(boolean mayInterruptIfRunning) { int s; boolean isCancelled; Thread t; ForkJoinPool p; DelayScheduler ds; @@ -490,7 +506,9 @@ public final long getDelay(TimeUnit unit) { return unit.convert(when - DelayScheduler.now(), NANOSECONDS); } public int compareTo(Delayed other) { // never used internally - long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS); + 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; } } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index c31dec0a81b3d..4be06ecc3bd75 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -52,7 +52,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.vm.SharedThreadContainer; -import static java.util.concurrent.DelayScheduler.DelayedTask; +import static java.util.concurrent.DelayScheduler.ScheduledForkJoinTask; /** * An {@link ExecutorService} for running {@link ForkJoinTask}s. @@ -140,7 +140,9 @@ * ScheduledExecutorService} methods to delay or periodically execute * tasks, as well as method {#link #submitWithTimeout} to cancel tasks * that take too long. The scheduled functions or actions may create - * and invoke other {@linkplain ForkJoinTask ForkJoinTasks}. Resource + * and invoke other {@linkplain ForkJoinTask ForkJoinTasks}. The + * schedule methods return {@linkplain ForkJoinTask ForkJoinTasks} + * that implement the {@link ScheduledFuture} interface. Resource * exhaustion encountered after initial submission results in task * cancellation. When time-based methods are used, shutdown policies * are based on the default policies of class {@link @@ -914,15 +916,16 @@ public class ForkJoinPool extends AbstractExecutorService * methods (via startDelayScheduler, with callback * onDelaySchedulerStart). The scheduler operates independently in * its own thread, relaying tasks to the pool to execute as they - * become ready (see method executeReadyDelayedTask). The only - * other interactions with the delayScheduler are to control - * shutdown and maintain shutdown-related policies in methods - * quiescent() and tryTerminate(). In particular, to conform to - * policies, shutdown-related processing must deal with cases in - * which tasks are submitted before shutdown, but not ready until - * afterwards, in which case they must bypass some screening to be - * allowed to run. Conversely, the DelayScheduler interacts with - * the pool only to check runState status and complete termination. + * become ready (see method + * executeReadyScheculedTask). The only other interactions + * with the delayScheduler are to control shutdown and maintain + * shutdown-related policies in methods quiescent() and + * tryTerminate(). In particular, to conform to policies, + * shutdown-related processing must deal with cases in which tasks + * are submitted before shutdown, but not ready until afterwards, + * in which case they must bypass some screening to be allowed to + * run. Conversely, the DelayScheduler interacts with the pool + * only to check runState status and complete termination. * * Memory placement * ================ @@ -3424,7 +3427,7 @@ public T invokeAny(Collection> tasks, // Support for delayed tasks - // methods used by Delayscheduler + // methods used by DelayScheduler static ForkJoinPool asyncCommonPool() { // override parallelism == 0 ForkJoinPool cp; @@ -3433,25 +3436,13 @@ static ForkJoinPool asyncCommonPool() { // override parallelism == 0 return cp; } - /** - * Returns status code for DelayScheduler: - * * 0 not shutdown - * * -1 stopping - * * 1 shutdown without cancelling delayed tasks - * * 2 shutdown cancelling delayed tasks - */ - final int delaySchedulerRunStatus() { - long rs; - return ((((rs = runState) & SHUTDOWN) == 0L) ? 0 : - (rs & STOP) != 0L ? -1 : - cancelDelayedTasksOnShutdown ? 2 : 1); - } - private DelayScheduler startDelayScheduler() { DelayScheduler ds; if ((ds = delayScheduler) == null) { boolean start = false; String name = poolName + "-delayScheduler"; + if (workerNamePrefix == null) + asyncCommonPool(); // override common parallelism zero lockRunState(); try { if ((ds = delayScheduler) == null) { @@ -3463,8 +3454,6 @@ private DelayScheduler startDelayScheduler() { } if (start) { // start outside of lock SharedThreadContainer ctr; - if (this == common) // override parallelism 0 - asyncCommonPool(); if ((ctr = container) != null) ctr.start(ds); else @@ -3484,23 +3473,23 @@ final void onDelaySchedulerStart() { } /** - * Arrange delayed execution of a DelayedTask via the - * DelayScheduler, creating and starting it if necessary. + * Returns status code for DelayScheduler: + * * 0 not shutdown + * * -1 stopping + * * 1 shutdown without cancelling delayed tasks + * * 2 shutdown cancelling delayed tasks */ - final void scheduleDelayedTask(DelayedTask task, long nanoDelay) { - DelayScheduler ds; - if (((ds = delayScheduler) == null && - (ds = startDelayScheduler()) == null) || - task == null || (runState & SHUTDOWN) != 0L) - throw new RejectedExecutionException(); - task.when = DelayScheduler.now() + Math.max(0L, nanoDelay); - ds.pend(task); + final int delaySchedulerRunStatus() { + long rs; + return ((((rs = runState) & SHUTDOWN) == 0L) ? 0 : + (rs & STOP) != 0L ? -1 : + cancelDelayedTasksOnShutdown ? 2 : 1); } /** * Arranges execution of a ready task from DelayScheduler */ - final void executeReadyDelayedTask(DelayedTask task) { + final void executeReadyScheduledTask(ScheduledForkJoinTask task) { if (task != null) { WorkQueue q; boolean cancel = false; @@ -3518,63 +3507,73 @@ final void executeReadyDelayedTask(DelayedTask task) { } /** - * Body of a DelayedTask serving to cancel another task on timeout + * Arrange delayed execution of a ScheduledForkJoinTask via the + * DelayScheduler, creating and starting it if necessary. + * @return the task */ - static final class CancelAction implements Runnable { - Future task; // set after construction - public void run() { - Future t; - if ((t = task) != null) - t.cancel(true); - } + final ScheduledForkJoinTask scheduleDelayedTask(ScheduledForkJoinTask task) { + DelayScheduler ds; + if (((ds = delayScheduler) == null && + (ds = startDelayScheduler()) == null) || + task == null || (runState & SHUTDOWN) != 0L) + throw new RejectedExecutionException(); + ds.pend(task); + return task; } public ScheduledFuture schedule(Runnable command, - long delay, - TimeUnit unit) { + long delay, TimeUnit unit) { Objects.requireNonNull(command); - DelayedTask t = new DelayedTask( - command, null, this, false, 0L, unit.toNanos(delay)); - t.schedule(); - return t; + return scheduleDelayedTask( + new ScheduledForkJoinTask( + Math.max(unit.toNanos(delay), 0L) + DelayScheduler.now(), + 0L, false, command, null, this)); } public ScheduledFuture schedule(Callable callable, - long delay, - TimeUnit unit) { + long delay, TimeUnit unit) { Objects.requireNonNull(callable); - DelayedTask t = new DelayedTask( - null, callable, this, false, 0L, unit.toNanos(delay)); - t.schedule(); - return t; + return scheduleDelayedTask( + new ScheduledForkJoinTask( + Math.max(unit.toNanos(delay), 0L) + DelayScheduler.now(), + 0L, false, null, callable, this)); } public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, - long period, - TimeUnit unit) { + long period, TimeUnit unit) { Objects.requireNonNull(command); if (period <= 0L) throw new IllegalArgumentException(); - long p = -unit.toNanos(period); // negative for fixed rate - DelayedTask t = new DelayedTask( - command, null, this, false, p, unit.toNanos(initialDelay)); - t.schedule(); - return t; + return scheduleDelayedTask( + new ScheduledForkJoinTask( + Math.max(unit.toNanos(initialDelay), 0L) + DelayScheduler.now(), + -unit.toNanos(period), // negative for fixed delay + false, command, null, this)); } public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, - long delay, - TimeUnit unit) { + long delay, TimeUnit unit) { Objects.requireNonNull(command); if (delay <= 0L) throw new IllegalArgumentException(); - long p = unit.toNanos(delay); - DelayedTask t = new DelayedTask( - command, null, this, false, p, unit.toNanos(initialDelay)); - t.schedule(); - return t; + return scheduleDelayedTask( + new ScheduledForkJoinTask( + Math.max(unit.toNanos(initialDelay), 0L) + DelayScheduler.now(), + unit.toNanos(delay), false, command, null, this)); + } + + /** + * Body of a ScheduledForkJoinTask serving to cancel another task on timeout + */ + static final class CancelAction implements Runnable { + Future task; // set after construction + public void run() { + Future t; + if ((t = task) != null) + t.cancel(true); + } } /** @@ -3602,17 +3601,16 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, * @throws NullPointerException if callable or unit is null */ public ForkJoinTask submitWithTimeout(Callable callable, - long timeout, - TimeUnit unit) { - ForkJoinTask.CallableWithCanceller task; + long timeout, TimeUnit unit) { + ForkJoinTask.CallableWithCanceller task; CancelAction onTimeout; Objects.requireNonNull(callable); - long d = unit.toNanos(timeout); - CancelAction onTimeout = new CancelAction(); - DelayedTask canceller = - new DelayedTask(onTimeout, null, this, true, 0L, d); + ScheduledForkJoinTask canceller = + new ScheduledForkJoinTask( + Math.max(unit.toNanos(timeout), 0L) + DelayScheduler.now(), 0L, + true, onTimeout = new CancelAction(), null, this); onTimeout.task = task = new ForkJoinTask.CallableWithCanceller(callable, canceller); - canceller.schedule(); + scheduleDelayedTask(canceller); poolSubmit(true, task); return task; } @@ -3622,9 +3620,9 @@ public ForkJoinTask submitWithTimeout(Callable callable, * not already been enabled for execution are not executed and * will be cancelled upon {@link #shutdown}. This method may be * invoked either before {@link #shutdown} to take effect upon the - * next call, or afterwards, to cancel such tasks, that may allow - * termination. Note that the next executions of periodic tasks - * are always disabled upon shutdown, so this method applies + * next call, or afterwards to cancel such tasks, which may then + * allow termination. Note that the next executions of periodic + * tasks are always disabled upon shutdown, so this method applies * meaningfully only to non-periodic tasks. */ public void cancelDelayedTasksOnShutdown() { From 0e139558dedd0de003f19a3e91e803433d767b19 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sat, 15 Feb 2025 11:06:21 -0500 Subject: [PATCH 26/40] Better accommodate CompletableFuture; use 4-ary heap; add javadocs; other misc --- .../util/concurrent/CompletableFuture.java | 186 +++++++------- .../java/util/concurrent/DelayScheduler.java | 240 ++++++++++-------- .../java/util/concurrent/ForkJoinPool.java | 176 ++++++++++--- .../java/util/concurrent/ForkJoinTask.java | 10 +- 4 files changed, 367 insertions(+), 245 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java index 619aac3615e6e..153f8530c1a90 100644 --- a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java +++ b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java @@ -46,6 +46,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.Objects; +import static java.util.concurrent.DelayScheduler.ScheduledForkJoinTask; /** * A {@link Future} that may be explicitly completed (setting its @@ -69,17 +70,15 @@ * a completion method. * *

  • All async methods without an explicit Executor - * argument are performed using the {@link ForkJoinPool#commonPool()}. - * This may be - * overridden for non-static methods in subclasses by defining method - * {@link #defaultExecutor()}. To simplify monitoring, debugging, - * and tracking, all generated asynchronous tasks are instances of the - * marker interface {@link AsynchronousCompletionTask}. Operations - * with time-delays can use adapter methods defined in this class, for - * example: {@code supplyAsync(supplier, delayedExecutor(timeout, - * timeUnit))}. To support methods with delays and timeouts, this - * class maintains at most one daemon thread for triggering and - * cancelling actions, not for running them. + * argument, as well as those involving delays are performed using the + * {@link ForkJoinPool#commonPool()}. The default async Executor may + * be overridden for non-static methods in subclasses by defining + * method {@link #defaultExecutor()}. To simplify monitoring, + * debugging, and tracking, all generated asynchronous tasks are + * instances of the marker interface {@link + * AsynchronousCompletionTask}. Operations with time-delays can use + * adapter methods defined in this class, for example: {@code + * supplyAsync(supplier, delayedExecutor(timeout, timeUnit))}. * *
  • All CompletionStage methods are implemented independently of * other public methods, so the behavior of one method is not impacted @@ -476,20 +475,7 @@ public static interface AsynchronousCompletionTask { * Default Executor */ private static final ForkJoinPool ASYNC_POOL = - ForkJoinPool.asyncCommonPool(); - - static ScheduledFuture delay(Runnable command, long delay, - TimeUnit unit) { - return ASYNC_POOL.schedule(command, delay, unit); - } - - /** - * Null-checks user executor argument - */ - static Executor screenExecutor(Executor e) { - if (e == null) throw new NullPointerException(); - return e; - } + ForkJoinPool.asyncCommonPool(); // ensures minimal parallelism // Modes for Completion.tryFire. Signedness matters. static final int SYNC = 0; @@ -691,8 +677,8 @@ final CompletableFuture tryFire(int mode) { private CompletableFuture uniApplyStage( Executor e, Function f) { - if (f == null) throw new NullPointerException(); Object r; + Objects.requireNonNull(f); if ((r = result) != null) return uniApplyNow(r, e, f); CompletableFuture d = newIncompleteFuture(); @@ -764,8 +750,8 @@ final CompletableFuture tryFire(int mode) { private CompletableFuture uniAcceptStage(Executor e, Consumer f) { - if (f == null) throw new NullPointerException(); Object r; + Objects.requireNonNull(f); if ((r = result) != null) return uniAcceptNow(r, e, f); CompletableFuture d = newIncompleteFuture(); @@ -832,8 +818,8 @@ final CompletableFuture tryFire(int mode) { } private CompletableFuture uniRunStage(Executor e, Runnable f) { - if (f == null) throw new NullPointerException(); Object r; + Objects.requireNonNull(f); if ((r = result) != null) return uniRunNow(r, e, f); CompletableFuture d = newIncompleteFuture(); @@ -913,7 +899,7 @@ else if (x != ex) private CompletableFuture uniWhenCompleteStage( Executor e, BiConsumer f) { - if (f == null) throw new NullPointerException(); + Objects.requireNonNull(f); CompletableFuture d = newIncompleteFuture(); Object r; if ((r = result) == null) @@ -976,7 +962,7 @@ final boolean uniHandle(Object r, private CompletableFuture uniHandleStage( Executor e, BiFunction f) { - if (f == null) throw new NullPointerException(); + Objects.requireNonNull(f); CompletableFuture d = newIncompleteFuture(); Object r; if ((r = result) == null) @@ -1034,7 +1020,7 @@ final boolean uniExceptionally(Object r, private CompletableFuture uniExceptionallyStage( Executor e, Function f) { - if (f == null) throw new NullPointerException(); + Objects.requireNonNull(f); CompletableFuture d = newIncompleteFuture(); Object r; if ((r = result) == null) @@ -1094,7 +1080,7 @@ final CompletableFuture tryFire(int mode) { private CompletableFuture uniComposeExceptionallyStage( Executor e, Function> f) { - if (f == null) throw new NullPointerException(); + Objects.requireNonNull(f); CompletableFuture d = newIncompleteFuture(); Object r, s; Throwable x; if ((r = result) == null) @@ -1201,7 +1187,7 @@ final CompletableFuture tryFire(int mode) { private CompletableFuture uniComposeStage( Executor e, Function> f) { - if (f == null) throw new NullPointerException(); + Objects.requireNonNull(f); CompletableFuture d = newIncompleteFuture(); Object r, s; Throwable x; if ((r = result) == null) @@ -1812,7 +1798,7 @@ public void run() { static CompletableFuture asyncSupplyStage(Executor e, Supplier f) { - if (f == null) throw new NullPointerException(); + Objects.requireNonNull(f); CompletableFuture d = new CompletableFuture(); e.execute(new AsyncSupply(d, f)); return d; @@ -1848,7 +1834,7 @@ public void run() { } static CompletableFuture asyncRunStage(Executor e, Runnable f) { - if (f == null) throw new NullPointerException(); + Objects.requireNonNull(f); CompletableFuture d = new CompletableFuture(); e.execute(new AsyncRun(d, f)); return d; @@ -2037,7 +2023,7 @@ public static CompletableFuture supplyAsync(Supplier supplier) { */ public static CompletableFuture supplyAsync(Supplier supplier, Executor executor) { - return asyncSupplyStage(screenExecutor(executor), supplier); + return asyncSupplyStage(Objects.requireNonNull(executor), supplier); } /** @@ -2065,7 +2051,7 @@ public static CompletableFuture runAsync(Runnable runnable) { */ public static CompletableFuture runAsync(Runnable runnable, Executor executor) { - return asyncRunStage(screenExecutor(executor), runnable); + return asyncRunStage(Objects.requireNonNull(executor), runnable); } /** @@ -2230,7 +2216,7 @@ public boolean complete(T value) { * to transition to a completed state, else {@code false} */ public boolean completeExceptionally(Throwable ex) { - if (ex == null) throw new NullPointerException(); + Objects.requireNonNull(ex); boolean triggered = internalComplete(new AltResult(ex)); postComplete(); return triggered; @@ -2248,7 +2234,7 @@ public CompletableFuture thenApplyAsync( public CompletableFuture thenApplyAsync( Function fn, Executor executor) { - return uniApplyStage(screenExecutor(executor), fn); + return uniApplyStage(Objects.requireNonNull(executor), fn); } public CompletableFuture thenAccept(Consumer action) { @@ -2261,7 +2247,7 @@ public CompletableFuture thenAcceptAsync(Consumer action) { public CompletableFuture thenAcceptAsync(Consumer action, Executor executor) { - return uniAcceptStage(screenExecutor(executor), action); + return uniAcceptStage(Objects.requireNonNull(executor), action); } public CompletableFuture thenRun(Runnable action) { @@ -2274,7 +2260,7 @@ public CompletableFuture thenRunAsync(Runnable action) { public CompletableFuture thenRunAsync(Runnable action, Executor executor) { - return uniRunStage(screenExecutor(executor), action); + return uniRunStage(Objects.requireNonNull(executor), action); } public CompletableFuture thenCombine( @@ -2292,7 +2278,7 @@ public CompletableFuture thenCombineAsync( public CompletableFuture thenCombineAsync( CompletionStage other, BiFunction fn, Executor executor) { - return biApplyStage(screenExecutor(executor), other, fn); + return biApplyStage(Objects.requireNonNull(executor), other, fn); } public CompletableFuture thenAcceptBoth( @@ -2310,7 +2296,7 @@ public CompletableFuture thenAcceptBothAsync( public CompletableFuture thenAcceptBothAsync( CompletionStage other, BiConsumer action, Executor executor) { - return biAcceptStage(screenExecutor(executor), other, action); + return biAcceptStage(Objects.requireNonNull(executor), other, action); } public CompletableFuture runAfterBoth(CompletionStage other, @@ -2326,7 +2312,7 @@ public CompletableFuture runAfterBothAsync(CompletionStage other, public CompletableFuture runAfterBothAsync(CompletionStage other, Runnable action, Executor executor) { - return biRunStage(screenExecutor(executor), other, action); + return biRunStage(Objects.requireNonNull(executor), other, action); } public CompletableFuture applyToEither( @@ -2342,7 +2328,7 @@ public CompletableFuture applyToEitherAsync( public CompletableFuture applyToEitherAsync( CompletionStage other, Function fn, Executor executor) { - return orApplyStage(screenExecutor(executor), other, fn); + return orApplyStage(Objects.requireNonNull(executor), other, fn); } public CompletableFuture acceptEither( @@ -2358,7 +2344,7 @@ public CompletableFuture acceptEitherAsync( public CompletableFuture acceptEitherAsync( CompletionStage other, Consumer action, Executor executor) { - return orAcceptStage(screenExecutor(executor), other, action); + return orAcceptStage(Objects.requireNonNull(executor), other, action); } public CompletableFuture runAfterEither(CompletionStage other, @@ -2374,7 +2360,7 @@ public CompletableFuture runAfterEitherAsync(CompletionStage other, public CompletableFuture runAfterEitherAsync(CompletionStage other, Runnable action, Executor executor) { - return orRunStage(screenExecutor(executor), other, action); + return orRunStage(Objects.requireNonNull(executor), other, action); } public CompletableFuture thenCompose( @@ -2390,7 +2376,7 @@ public CompletableFuture thenComposeAsync( public CompletableFuture thenComposeAsync( Function> fn, Executor executor) { - return uniComposeStage(screenExecutor(executor), fn); + return uniComposeStage(Objects.requireNonNull(executor), fn); } public CompletableFuture whenComplete( @@ -2405,7 +2391,7 @@ public CompletableFuture whenCompleteAsync( public CompletableFuture whenCompleteAsync( BiConsumer action, Executor executor) { - return uniWhenCompleteStage(screenExecutor(executor), action); + return uniWhenCompleteStage(Objects.requireNonNull(executor), action); } public CompletableFuture handle( @@ -2420,7 +2406,7 @@ public CompletableFuture handleAsync( public CompletableFuture handleAsync( BiFunction fn, Executor executor) { - return uniHandleStage(screenExecutor(executor), fn); + return uniHandleStage(Objects.requireNonNull(executor), fn); } /** @@ -2450,7 +2436,7 @@ public CompletableFuture exceptionallyAsync( */ public CompletableFuture exceptionallyAsync( Function fn, Executor executor) { - return uniExceptionallyStage(screenExecutor(executor), fn); + return uniExceptionallyStage(Objects.requireNonNull(executor), fn); } /** @@ -2475,7 +2461,7 @@ public CompletableFuture exceptionallyComposeAsync( public CompletableFuture exceptionallyComposeAsync( Function> fn, Executor executor) { - return uniComposeExceptionallyStage(screenExecutor(executor), fn); + return uniComposeExceptionallyStage(Objects.requireNonNull(executor), fn); } /* ------------- Arbitrary-arity constructions -------------- */ @@ -2641,8 +2627,7 @@ public void obtrudeValue(T value) { * @throws NullPointerException if the exception is null */ public void obtrudeException(Throwable ex) { - if (ex == null) throw new NullPointerException(); - result = new AltResult(ex); + result = new AltResult(Objects.requireNonNull(ex)); postComplete(); } @@ -2773,9 +2758,8 @@ public CompletionStage minimalCompletionStage() { */ public CompletableFuture completeAsync(Supplier supplier, Executor executor) { - if (supplier == null || executor == null) - throw new NullPointerException(); - executor.execute(new AsyncSupply(this, supplier)); + executor.execute(new AsyncSupply( + this, Objects.requireNonNull(supplier))); return this; } @@ -2806,11 +2790,8 @@ public CompletableFuture completeAsync(Supplier supplier) { * @since 9 */ public CompletableFuture orTimeout(long timeout, TimeUnit unit) { - if (unit == null) - throw new NullPointerException(); - if (result == null) - whenComplete(new Canceller(delay(new Timeout(this), - timeout, unit))); + arrangeTimeout(unit.toNanos(timeout), + new Timeout(this, null, true)); return this; } @@ -2828,15 +2809,25 @@ public CompletableFuture orTimeout(long timeout, TimeUnit unit) { */ public CompletableFuture completeOnTimeout(T value, long timeout, TimeUnit unit) { - if (unit == null) - throw new NullPointerException(); - if (result == null) - whenComplete(new Canceller(delay( - new DelayedCompleter(this, value), - timeout, unit))); + arrangeTimeout(unit.toNanos(timeout), + new Timeout(this, value, false)); return this; } + /** + * Schedules a timeout action, as well as whenComplete handling to + * cancel the action if not needed. + */ + private void arrangeTimeout(long nanoDelay, Timeout onTimeout) { + ForkJoinPool e = ASYNC_POOL; + if (result == null) { + ScheduledForkJoinTask t = new ScheduledForkJoinTask( + nanoDelay, 0L, false, onTimeout, null, e); + whenComplete(new Canceller(t)); + e.scheduleDelayedTask(t); + } + } + /** * Returns a new Executor that submits a task to the given base * executor after the given delay (or no delay if non-positive). @@ -2852,9 +2843,8 @@ public CompletableFuture completeOnTimeout(T value, long timeout, */ public static Executor delayedExecutor(long delay, TimeUnit unit, Executor executor) { - if (unit == null || executor == null) - throw new NullPointerException(); - return new DelayedExecutor(delay, unit, executor); + return new DelayedExecutor(unit.toNanos(delay), + Objects.requireNonNull(executor)); } /** @@ -2870,9 +2860,7 @@ public static Executor delayedExecutor(long delay, TimeUnit unit, * @since 9 */ public static Executor delayedExecutor(long delay, TimeUnit unit) { - if (unit == null) - throw new NullPointerException(); - return new DelayedExecutor(delay, unit, ASYNC_POOL); + return new DelayedExecutor(unit.toNanos(delay), ASYNC_POOL); } /** @@ -2899,8 +2887,7 @@ public static CompletionStage completedStage(U value) { * @since 9 */ public static CompletableFuture failedFuture(Throwable ex) { - if (ex == null) throw new NullPointerException(); - return new CompletableFuture(new AltResult(ex)); + return new CompletableFuture(new AltResult(Objects.requireNonNull(ex))); } /** @@ -2914,21 +2901,23 @@ public static CompletableFuture failedFuture(Throwable ex) { * @since 9 */ public static CompletionStage failedStage(Throwable ex) { - if (ex == null) throw new NullPointerException(); - return new MinimalStage(new AltResult(ex)); + return new MinimalStage(new AltResult(Objects.requireNonNull(ex))); } // Little class-ified lambdas to better support monitoring static final class DelayedExecutor implements Executor { - final long delay; - final TimeUnit unit; + final long nanoDelay; final Executor executor; - DelayedExecutor(long delay, TimeUnit unit, Executor executor) { - this.delay = delay; this.unit = unit; this.executor = executor; + DelayedExecutor(long nanoDelay, Executor executor) { + this.nanoDelay = nanoDelay; this.executor = executor; } public void execute(Runnable r) { - delay(new TaskSubmitter(executor, r), delay, unit); + ForkJoinPool e = ASYNC_POOL; // Use immediate mode to relay task + e.scheduleDelayedTask( + new ScheduledForkJoinTask( + nanoDelay, 0L, true, + new TaskSubmitter(executor, r), null, e)); } } @@ -2943,24 +2932,21 @@ static final class TaskSubmitter implements Runnable { public void run() { executor.execute(action); } } - /** Action to completeExceptionally on timeout */ - static final class Timeout implements Runnable { - final CompletableFuture f; - Timeout(CompletableFuture f) { this.f = f; } - public void run() { - if (f != null && !f.isDone()) - f.completeExceptionally(new TimeoutException()); - } - } - - /** Action to complete on timeout */ - static final class DelayedCompleter implements Runnable { + /** Action to complete (possibly exceptionally) on timeout */ + static final class Timeout implements Runnable { final CompletableFuture f; - final U u; - DelayedCompleter(CompletableFuture f, U u) { this.f = f; this.u = u; } + final U value; + final boolean exceptional; + Timeout(CompletableFuture f, U value, boolean exceptional) { + this.f = f; this.value = value; this.exceptional = exceptional; + } public void run() { - if (f != null) - f.complete(u); + if (f != null && !f.isDone()) { + if (exceptional) + f.completeExceptionally(new TimeoutException()); + else + f.complete(value); + } } } @@ -2969,7 +2955,7 @@ static final class Canceller implements BiConsumer { final Future f; Canceller(Future f) { this.f = f; } public void accept(Object ignore, Throwable ex) { - if (f != null && !f.isDone()) + if (f != null) f.cancel(false); } } diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index e04c055a68a0e..a06002cb6ba7c 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -47,10 +47,16 @@ final class DelayScheduler extends Thread { /* - * A DelayScheduler maintains a binary heap based on trigger times - * (field ScheduledForkJoinTask.when) along with a pending queue - * of tasks submitted by other threads. When ready, tasks are - * relayed to the pool (or run directly if in task.isImmediate). + * 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 ready, tasks + * are relayed to the pool, or run directly if task.isImmediate. + * Immediate mode is designed for internal jdk usages in which the + * (known, non-blocking) action is to cancel, unblock or + * differently relay another async task. 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 @@ -63,38 +69,44 @@ final class DelayScheduler extends Thread { * 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. + * but much less so versus the scheduler. It is possible to use + * multiple pending queues to reduce this from of contention but + * it doesn't seem worthwhile even under heavy loads. * - * 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 tryTerminate - * checks as well as to suppress reactivation while terminating. + * The implementation relies on the scheduler being a non-virtual + * 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 + * tryTerminate checks as well as to suppress reactivation while + * terminating. * * The implementation is designed to accommodate usages in which * many or even most tasks are cancelled before executing (mainly - * IO-based timeouts). Cancellations are added to the pending - * queue in method ScheduledForkJoinTask.cancel(), to remove them - * from the heap. (This requires some safeguards to deal with - * tasks cancelled while they are still pending.) In addition, - * the heap replace method removes any cancelled tasks seen while - * performing sift-down operations, in which case elements are - * removed even before processing the removal request (which is - * then a no-op). + * IO-based timeouts). The use of a 4-ary heap (in which each + * element has up to 4 children) improves locality and reduces + * movement and memory writes compared to a standard binary heap, + * at the expense of more expensive replace() operations. + * (Overall, about half the writes but twice the reads). + * Especially in the presence of cancellations, this is often + * faster because the replace method removes any 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 + * ScheduledForkJoinTask.cancel() (which is then a no-op if + * generated at all). * - * To ensure that comparisons do not encounter integer wrap + * 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. * - * For the sake of compatibility with ScheduledThreadPoolExecutor, - * shutdown follows the same rules, which add some further steps - * beyond the cleanup associated with shutdownNow. Upon noticing - * pool shutdown, delayed and/or periodic tasks are purged; the + * 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 @@ -102,23 +114,25 @@ final class DelayScheduler extends Thread { * be a lag setting their status). */ - private static final int INITIAL_HEAP_CAPACITY = 1 << 6; private ForkJoinPool pool; // read once and detached upon starting - volatile ScheduledForkJoinTask pending; // for submited adds and removes + volatile ScheduledForkJoinTask pending; // for submitted adds and removes volatile int active; // 0: inactive, -1: stopped, +1: running int restingSize; // written only before parking + private volatile int cancelDelayedTasksOnShutdown; // policy control + + 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; + private static final Unsafe U; // for atomic operations private static final long ACTIVE; private static final long PENDING; - static final long nanoTimeOffset; static { U = Unsafe.getUnsafe(); Class klass = DelayScheduler.class; ACTIVE = U.objectFieldOffset(klass, "active"); PENDING = U.objectFieldOffset(klass, "pending"); - long ns = System.nanoTime(); // ensure negative to avoid overflow - nanoTimeOffset = Long.MIN_VALUE + Math.min(ns, 0L); } DelayScheduler(ForkJoinPool p, String name) { @@ -135,8 +149,8 @@ static final long now() { } /** - * Ensure active, unparking if necessary, unless stopped. - * Returns the status as observed prior to activating + * Ensures the scheduler is not parked unless stopped. + * Returns negative if already stopped */ final int ensureActive() { int state; @@ -146,8 +160,8 @@ final int ensureActive() { } /** - * Inserts the task to pending queue, to add, remove, or ignore - * depending on task status when processed. + * Inserts the task (if non-null) to pending queue, to add, + * remove, or ignore depending on task status when processed. */ final void pend(ScheduledForkJoinTask task) { ScheduledForkJoinTask f = pending; @@ -174,13 +188,23 @@ final int approximateSize() { return (active < 0) ? 0 : restingSize; } + /** + * Turns on cancelDelayedTasksOnShutdown policy + */ + final void cancelDelayedTasksOnShutdown() { + cancelDelayedTasksOnShutdown = 1; + ensureActive(); + } + /** * Sets up and runs scheduling loop */ public final void run() { ForkJoinPool p = pool; - pool = null; // detach - if (p != null) { // currently always true + pool = null; // detach + if (p == null) // failed initialization + active = -1; + else { try { loop(p); } finally { @@ -193,7 +217,7 @@ public final void run() { /** * After initialization, repeatedly: - * 1. Process pending tasks in batches, to add or remove from heap, + * 1. Process pending tasks in batches, to add or remove from heap * 2. Check for shutdown, either exiting or preparing for shutdown when empty * 3. Trigger all ready tasks by externally submitting them to pool * 4. If active, set tentatively inactive, @@ -229,7 +253,7 @@ else if (stat >= 0) { ScheduledForkJoinTask[] nh; int k = n++, pk, nc; while (k > 0 && - (parent = h[pk = (k - 1) >>> 1]) != null && + (parent = h[pk = (k - 1) >>> 2]) != null && (parent.when > d)) { parent.heapIndex = k; h[k] = parent; @@ -237,7 +261,7 @@ else if (stat >= 0) { } t.heapIndex = k; h[k] = t; - if (n >= cap && (nh = growHeap(h, cap)) != null && + if (n >= cap && (nh = growHeap(h)) != null && (nc = nh.length) > cap) { cap = nc; // else keep using old array h = nh; @@ -278,20 +302,19 @@ else if (stat >= 0) { Thread.interrupted(); // clear before park if (active == 0) U.park(false, parkTime); - else + else // deactivate and recheck U.compareAndSetInt(this, ACTIVE, 1, 0); } } } /** - * Tries to reallocate the heap array; returning null on failure + * Tries to reallocate the heap array, returning null on failure */ - private ScheduledForkJoinTask[] growHeap(ScheduledForkJoinTask[] h, - int cap) { - int newCap = cap << 1; + private ScheduledForkJoinTask[] growHeap(ScheduledForkJoinTask[] h) { + int cap, newCap; ScheduledForkJoinTask[] nh = null; - if (h != null && h.length == cap && cap < newCap) { + if (h != null && (cap = h.length) < (newCap = cap << 1)) { try { nh = Arrays.copyOf(h, newCap); } catch (Error | RuntimeException ex) { @@ -322,31 +345,32 @@ private static int replace(ScheduledForkJoinTask[] h, int k, int n) { u.heapIndex = -1; } } - if (t != null) { // sift down - int ck, rk; long cd, rd; ScheduledForkJoinTask c, r; - while ((ck = (k << 1) + 1) < n && ck >= 0 && - (c = h[ck]) != null) { - cd = c.when; - if (c.status < 0 && alsoReplace < 0) { - alsoReplace = ck; // at most one per pass - c.heapIndex = -1; - cd = Long.MAX_VALUE; // prevent swap below - } - if ((rk = ck + 1) < n && (r = h[rk]) != null) { - rd = r.when; - if (r.status < 0 && alsoReplace < 0) { - alsoReplace = rk; - r.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 (rd < cd) { // use right child - cd = rd; c = r; ck = rk; + else if (leastChild == null || cd < leastValue) { + leastValue = cd; + leastIndex = ck; + leastChild = c; } + if (--j == 0 || ++ck >= n) + break; } - if (d <= cd) + if (leastChild == null || d <= leastValue) break; - c.heapIndex = k; - h[k] = c; - k = ck; + leastChild.heapIndex = k; + h[k] = leastChild; + k = leastIndex; } t.heapIndex = k; } @@ -358,19 +382,23 @@ else if (rd < cd) { // use right child } /** - * Call only when pool is shutdown. If called when 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. + * 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 > 0) { + if ((runStatus & POOL_STOPPING) == 0) { if (n > 0) { - if (runStatus > 1) - n = cancelAll(h, n); - else if (runStatus != prevRunStatus && h != null && h.length >= n) { + 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 && @@ -384,7 +412,7 @@ else if (runStatus != prevRunStatus && h != null && h.length >= n) { } } if (n > 0 || p == null || !p.tryStopIfEnabled()) - return n; + return n; // check for quiescent shutdown } if (n > 0) cancelAll(h, n); @@ -395,7 +423,7 @@ else if (runStatus != prevRunStatus && h != null && h.length >= n) { return -1; } - private int cancelAll(ScheduledForkJoinTask[] h, int n) { + private void cancelAll(ScheduledForkJoinTask[] h, int n) { if (h != null && h.length >= n) { ScheduledForkJoinTask t; for (int i = 0; i < n; ++i) { @@ -406,33 +434,44 @@ private int cancelAll(ScheduledForkJoinTask[] h, int n) { } } } - return 0; } /** * Task class for DelayScheduler operations */ - @SuppressWarnings("serial") + @SuppressWarnings("serial") // Not designed to be serializable static final class ScheduledForkJoinTask extends ForkJoinTask.InterruptibleTask implements ScheduledFuture { + final ForkJoinPool pool; // must be nonnull unless uncancellable final Runnable runnable; // only one of runnable or callable nonnull final Callable callable; - final ForkJoinPool pool; T result; - ScheduledForkJoinTask nextPending; // for DelayScheduler submissions + 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 - public ScheduledForkJoinTask(long when, long nextDelay, boolean isImmediate, - Runnable runnable, Callable callable, - ForkJoinPool pool) { - heapIndex = -1; - this.when = when; - this.isImmediate = isImmediate; + /** + * 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 (Runnabke) 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 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; @@ -442,14 +481,11 @@ public void schedule() { // relay to pool, to allow independent use pool.scheduleDelayedTask(this); } - @Override + // ForkJoinTask methods public final T getRawResult() { return result; } - @Override public final void setRawResult(T v) { result = v; } - @Override - final Object adaptee() { return (runnable != null) ? runnable : callable; } - @Override + // InterruptibleTask methods final T compute() throws Exception { Callable c; Runnable r; T res = null; @@ -460,10 +496,10 @@ else if ((c = callable) != null) return res; } - @Override - final boolean postExec() { // resubmit if periodic + final boolean postExec() { // possibly resubmit long d; ForkJoinPool p; DelayScheduler ds; - if ((d = nextDelay) != 0L && status >= 0 && + if ((d = nextDelay) != 0L && // is periodic + status >= 0 && // not abnormally completed (p = pool) != null && (ds = p.delayScheduler) != null) { if (p.delaySchedulerRunStatus() == 0) { heapIndex = -1; @@ -474,18 +510,17 @@ final boolean postExec() { // resubmit if periodic ds.pend(this); return false; } - trySetCancelled(); + trySetCancelled(); // pool is shutdown } return true; } - @Override public final boolean cancel(boolean mayInterruptIfRunning) { - int s; boolean isCancelled; Thread t; - ForkJoinPool p; DelayScheduler ds; + int s; boolean isCancelled; if ((s = trySetCancelled()) < 0) isCancelled = ((s & (ABNORMAL | THROWN)) == ABNORMAL); else { + Thread t; ForkJoinPool p; DelayScheduler ds; isCancelled = true; if ((t = runner) != null) { if (mayInterruptIfRunning) { @@ -502,6 +537,9 @@ else if (heapIndex >= 0 && nextPending == null && return isCancelled; } + final Object adaptee() { return (runnable != null) ? runnable : callable; } + + // ScheduledFuture methods public final long getDelay(TimeUnit unit) { return unit.convert(when - DelayScheduler.now(), NANOSECONDS); } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 4be06ecc3bd75..3ceba291aeb27 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -1652,7 +1652,6 @@ final boolean isApparentlyUnblocked() { final long config; // static configuration bits volatile long stealCount; // collects worker nsteals volatile long threadIds; // for worker thread names - volatile boolean cancelDelayedTasksOnShutdown; @jdk.internal.vm.annotation.Contended("fjpctl") // segregate volatile long ctl; // main pool control @@ -3119,6 +3118,16 @@ public static ForkJoinPool commonPool() { return common; } + /** + * Package-private access to commonPool overriding zero parallelism + */ + static ForkJoinPool asyncCommonPool() { + ForkJoinPool cp; + if ((cp = common).parallelism == 0) + U.compareAndSetInt(cp, PARALLELISM, 0, 1); + return cp; + } + // Execution methods /** @@ -3427,15 +3436,9 @@ public T invokeAny(Collection> tasks, // Support for delayed tasks - // methods used by DelayScheduler - - static ForkJoinPool asyncCommonPool() { // override parallelism == 0 - ForkJoinPool cp; - if ((cp = common).parallelism == 0) - U.compareAndSetInt(cp, PARALLELISM, 0, 1); - return cp; - } - + /** + * Creates and starts Delayscheduler + */ private DelayScheduler startDelayScheduler() { DelayScheduler ds; if ((ds = delayScheduler) == null) { @@ -3453,6 +3456,7 @@ private DelayScheduler startDelayScheduler() { unlockRunState(); } if (start) { // start outside of lock + // exceptions on start passed to (external) callers SharedThreadContainer ctr; if ((ctr = container) != null) ctr.start(ds); @@ -3473,21 +3477,16 @@ final void onDelaySchedulerStart() { } /** - * Returns status code for DelayScheduler: - * * 0 not shutdown - * * -1 stopping - * * 1 shutdown without cancelling delayed tasks - * * 2 shutdown cancelling delayed tasks + * Returns STOP and SHUTDOWN status for DelayScheduler (zero if + * neither). */ final int delaySchedulerRunStatus() { - long rs; - return ((((rs = runState) & SHUTDOWN) == 0L) ? 0 : - (rs & STOP) != 0L ? -1 : - cancelDelayedTasksOnShutdown ? 2 : 1); + return (int)(runState & (SHUTDOWN | STOP)); } /** - * Arranges execution of a ready task from DelayScheduler + * Arranges execution of a ready task from DelayScheduler, or + * cancels it on error */ final void executeReadyScheduledTask(ScheduledForkJoinTask task) { if (task != null) { @@ -3499,7 +3498,7 @@ final void executeReadyScheduledTask(ScheduledForkJoinTask task) { else q.push(task, this, false); } catch(Error | RuntimeException ex) { - cancel = true; + cancel = true; // OOME or VM error } if (cancel) task.trySetCancelled(); @@ -3507,7 +3506,7 @@ final void executeReadyScheduledTask(ScheduledForkJoinTask task) { } /** - * Arrange delayed execution of a ScheduledForkJoinTask via the + * Arranges delayed execution of a ScheduledForkJoinTask via the * DelayScheduler, creating and starting it if necessary. * @return the task */ @@ -3521,24 +3520,96 @@ final ScheduledForkJoinTask scheduleDelayedTask(ScheduledForkJoinTask return task; } + /** + * Submits a one-shot task that becomes enabled after the given + * delay, At which point it will execute unless explicitly + * cancelled, or fail to execute (eventually reporting + * cancellation) when encountering resource exhaustion, or the + * pool is {@link #shutdownNow}, or is {@link #shutdown} when + * otherwise quiescent and {@link #cancelDelayedTasksOnShutdown} + * is in effect. + * + * @param command the task to execute + * @param delay the time from now to delay execution + * @param unit the time unit of the delay parameter + * @return a ForkJoinTask implementing the ScheduledFuture + * interface, whose {@code get()} method will return + * {@code null} upon normal completion. + * @throws RejectedExecutionException if the pool is shutdown or + * submission encounters resource exhaustion. + * @throws NullPointerException if command or unit is null + * @since 25 + */ public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { Objects.requireNonNull(command); return scheduleDelayedTask( new ScheduledForkJoinTask( - Math.max(unit.toNanos(delay), 0L) + DelayScheduler.now(), - 0L, false, command, null, this)); + unit.toNanos(delay), 0L, false, command, null, this)); } + /** + * Submits a value-returning one-shot task that becomes enabled + * after the given delay. At which point it will execute unless + * explicitly cancelled, or fail to execute (eventually reporting + * cancellation) when encountering resource exhaustion, or the + * pool is {@link #shutdownNow}, or is {@link #shutdown} when + * otherwise quiescent and {@link #cancelDelayedTasksOnShutdown} + * is in effect. + * + * @param callable the function to execute + * @param delay the time from now to delay execution + * @param unit the time unit of the delay parameter + * @param the type of the callable's result + * @return a ForkJoinTask implementing the ScheduledFuture + * interface, whose {@code get()} method will return the + * value from the callable upon normal completion. + * @throws RejectedExecutionException if the pool is shutdown or + * submission encounters resource exhaustion. + * @throws NullPointerException if command or unit is null + * @since 25 + */ public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { Objects.requireNonNull(callable); return scheduleDelayedTask( new ScheduledForkJoinTask( - Math.max(unit.toNanos(delay), 0L) + DelayScheduler.now(), - 0L, false, null, callable, this)); + unit.toNanos(delay), 0L, false, null, callable, this)); } + /** + * 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. + * + *

    The sequence of task executions continues indefinitely until + * one of the following exceptional completions occur: + *

      + *
    • The task is {@linkplain Future#cancel explicitly cancelled} + *
    • Method {@link #shutdownNow} is called + *
    • Method {@link #shutdown} is called and the pool is + * otherwise quiescent, in which case existing executions continue + * but subsequent executions do not. + *
    • An execution or the task encounters resource exhaustion. + *
    • 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. + *
    + * Subsequent executions are suppressed. Subsequent calls to + * {@link Future#isDone isDone()} on the returned future will + * return {@code true}. + * + *

    If any execution of this task takes longer than its period, then + * subsequent executions may start late, but will not concurrently + * execute. + * @throws RejectedExecutionException if the pool is shutdown or + * submission encounters resource exhaustion. + * @throws NullPointerException if command or unit is null + * @throws IllegalArgumentException if period less than or equal to zero + * @since 25 + */ public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { @@ -3547,11 +3618,38 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, throw new IllegalArgumentException(); return scheduleDelayedTask( new ScheduledForkJoinTask( - Math.max(unit.toNanos(initialDelay), 0L) + DelayScheduler.now(), - -unit.toNanos(period), // negative for fixed delay + unit.toNanos(initialDelay), + unit.toNanos(period), false, command, null, this)); } + /** + * 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. + *

    The sequence of task executions continues indefinitely until + * one of the following exceptional completions occur: + *

      + *
    • The task is {@linkplain Future#cancel explicitly cancelled} + *
    • Method {@link #shutdownNow} is called + *
    • Method {@link #shutdown} is called and the pool is + * otherwise quiescent, in which case existing executions continue + * but subsequent executions do not. + *
    • An execution or the task encounters resource exhaustion. + *
    • 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. + *
    + * Subsequent executions are suppressed. Subsequent calls to + * {@link Future#isDone isDone()} on the returned future will + * return {@code true}. + * @throws RejectedExecutionException if the pool is shutdown or + * submission encounters resource exhaustion. + * @throws NullPointerException if command or unit is null + * @throws IllegalArgumentException if delay less than or equal to zero + * @since 25 + */ public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { @@ -3560,8 +3658,9 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, throw new IllegalArgumentException(); return scheduleDelayedTask( new ScheduledForkJoinTask( - Math.max(unit.toNanos(initialDelay), 0L) + DelayScheduler.now(), - unit.toNanos(delay), false, command, null, this)); + unit.toNanos(initialDelay), + -unit.toNanos(delay), // negative for fixed delay + false, command, null, this)); } /** @@ -3599,6 +3698,7 @@ public void run() { * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if callable or unit is null + * @since 25 */ public ForkJoinTask submitWithTimeout(Callable callable, long timeout, TimeUnit unit) { @@ -3606,8 +3706,8 @@ public ForkJoinTask submitWithTimeout(Callable callable, Objects.requireNonNull(callable); ScheduledForkJoinTask canceller = new ScheduledForkJoinTask( - Math.max(unit.toNanos(timeout), 0L) + DelayScheduler.now(), 0L, - true, onTimeout = new CancelAction(), null, this); + unit.toNanos(timeout), 0L, true, + onTimeout = new CancelAction(), null, this); onTimeout.task = task = new ForkJoinTask.CallableWithCanceller(callable, canceller); scheduleDelayedTask(canceller); @@ -3617,19 +3717,20 @@ public ForkJoinTask submitWithTimeout(Callable callable, /** * Arranges that scheduled tasks that are not executing and have - * not already been enabled for execution are not executed and + * not already been enabled for execution will not be executed and * will be cancelled upon {@link #shutdown}. This method may be * invoked either before {@link #shutdown} to take effect upon the * next call, or afterwards to cancel such tasks, which may then - * allow termination. Note that the next executions of periodic + * allow termination. Note that subsequent executions of periodic * tasks are always disabled upon shutdown, so this method applies * meaningfully only to non-periodic tasks. + * @since 25 */ public void cancelDelayedTasksOnShutdown() { DelayScheduler ds; - cancelDelayedTasksOnShutdown = true; - if ((ds = delayScheduler) != null) - ds.ensureActive(); + if ((ds = delayScheduler) != null || + (ds = startDelayScheduler()) != null) + ds.cancelDelayedTasksOnShutdown(); } /** @@ -3812,6 +3913,7 @@ public int getQueuedSubmissionCount() { * delayed tasks are being processed. * * @return an estimate of the number of delayed tasks + * @since 25 */ public int getDelayedTaskCount() { DelayScheduler ds; diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 50462e33fe78e..7e09505d7d4d7 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1634,7 +1634,6 @@ abstract static class InterruptibleTask extends ForkJoinTask implements RunnableFuture { transient volatile Thread runner; abstract T compute() throws Exception; - abstract boolean postExec(); public final boolean exec() { Thread.interrupted(); Thread t = runner = Thread.currentThread(); @@ -1655,6 +1654,9 @@ public final boolean exec() { } return postExec(); } + boolean postExec() { // returns completion status to doExec + return true; + } public boolean cancel(boolean mayInterruptIfRunning) { int s; boolean isCancelled; Thread t; if ((s = trySetCancelled()) < 0) @@ -1696,7 +1698,6 @@ static final class AdaptedInterruptibleCallable extends InterruptibleTask public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } final T compute() throws Exception { return callable.call(); } - final boolean postExec() { return true; } final Object adaptee() { return callable; } private static final long serialVersionUID = 2838392045355241008L; } @@ -1717,7 +1718,6 @@ static final class AdaptedInterruptibleRunnable extends InterruptibleTask public final T getRawResult() { return result; } public final void setRawResult(T v) { } final T compute() { runnable.run(); return result; } - final boolean postExec() { return true; } final Object adaptee() { return runnable; } private static final long serialVersionUID = 2838392045355241008L; } @@ -1735,7 +1735,6 @@ static final class RunnableExecuteAction extends InterruptibleTask { public final Void getRawResult() { return null; } public final void setRawResult(Void v) { } final Void compute() { runnable.run(); return null; } - final boolean postExec() { return true; } final Object adaptee() { return runnable; } void onAuxExceptionSet(Throwable ex) { // if a handler, invoke it Thread t; java.lang.Thread.UncaughtExceptionHandler h; @@ -1783,7 +1782,6 @@ else if (U.getAndAddInt(this, COUNT, -1) <= 1) { } } public final T compute() { return null; } // never forked - final boolean postExec() { return true; } public final T getRawResult() { return result; } public final void setRawResult(T v) { } @@ -1859,7 +1857,6 @@ final void onRootCompletion() { } public final Void getRawResult() { return null; } public final void setRawResult(Void v) { } - final boolean postExec() { return true; } final Object adaptee() { return callable; } } @@ -1887,7 +1884,6 @@ final T compute() throws Exception { t.cancel(false); } } - final boolean postExec() { return true; } final Object adaptee() { return callable; } } From 14a7a6f47588c2e041de7e4bdd16e6ef54f108aa Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 16 Feb 2025 12:44:17 -0500 Subject: [PATCH 27/40] Reduce garbage retention; use trailing padding; add tests --- .../java/util/concurrent/DelayScheduler.java | 68 +++++++++++-------- .../java/util/concurrent/ForkJoinPool.java | 27 +++++++- .../java/util/concurrent/ForkJoinTask.java | 25 ++++--- .../concurrent/tck/ForkJoinPool20Test.java | 19 ++++++ 4 files changed, 97 insertions(+), 42 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index a06002cb6ba7c..f6f6d726e0f81 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -43,7 +43,6 @@ * An add-on for ForkJoinPools that provides scheduling for * delayed and periodic tasks */ -@jdk.internal.vm.annotation.Contended() final class DelayScheduler extends Thread { /* @@ -86,12 +85,12 @@ final class DelayScheduler extends Thread { * terminating. * * The implementation is designed to accommodate usages in which - * many or even most tasks are cancelled before executing (mainly - * IO-based timeouts). The use of a 4-ary heap (in which each - * element has up to 4 children) improves locality and reduces - * movement and memory writes compared to a standard binary heap, - * at the expense of more expensive replace() operations. - * (Overall, about half the writes but twice the reads). + * 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 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 any cancelled tasks * seen while performing sift-down operations, in which case these @@ -114,11 +113,13 @@ final class DelayScheduler extends Thread { * be a lag setting their status). */ - private ForkJoinPool pool; // read once and detached upon starting - volatile ScheduledForkJoinTask pending; // for submitted adds and removes + 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 - private volatile int cancelDelayedTasksOnShutdown; // policy control + volatile int cancelDelayedTasksOnShutdown; // policy control + // reduce trailing false sharing + int pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7, pad8, pad9, padA; private static final int INITIAL_HEAP_CAPACITY = 1 << 6; private static final int POOL_STOPPING = 1; // must match ForkJoinPool @@ -242,7 +243,7 @@ private void loop(ForkJoinPool p) { t.nextPending = null; if (i >= 0) { t.heapIndex = -1; - if (i < n && i < cap && h[i] == t) + if (i < cap && h[i] == t) n = replace(h, i, n); } else if (stat >= 0) { @@ -311,7 +312,7 @@ else if (stat >= 0) { /** * Tries to reallocate the heap array, returning null on failure */ - private ScheduledForkJoinTask[] growHeap(ScheduledForkJoinTask[] h) { + private static ScheduledForkJoinTask[] growHeap(ScheduledForkJoinTask[] h) { int cap, newCap; ScheduledForkJoinTask[] nh = null; if (h != null && (cap = h.length) < (newCap = cap << 1)) { @@ -423,7 +424,7 @@ else if (prevRunStatus == 0 && h != null && h.length >= n) { return -1; } - private void cancelAll(ScheduledForkJoinTask[] h, int n) { + private static void cancelAll(ScheduledForkJoinTask[] h, int n) { if (h != null && h.length >= n) { ScheduledForkJoinTask t; for (int i = 0; i < n; ++i) { @@ -443,9 +444,9 @@ private void cancelAll(ScheduledForkJoinTask[] h, int n) { static final class ScheduledForkJoinTask extends ForkJoinTask.InterruptibleTask implements ScheduledFuture { - final ForkJoinPool pool; // must be nonnull unless uncancellable - final Runnable runnable; // only one of runnable or callable nonnull - final Callable callable; + ForkJoinPool pool; // nulled out after use + Runnable runnable; // at most one of runnable or callable nonnull + Callable callable; T result; ScheduledForkJoinTask nextPending; // for DelayScheduler pending queue long when; // nanoTime-based trigger time @@ -458,7 +459,7 @@ static final class 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 (Runnabke) action is to be performed + * @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) @@ -478,14 +479,16 @@ public ScheduledForkJoinTask(long delay, long nextDelay, } public void schedule() { // relay to pool, to allow independent use - pool.scheduleDelayedTask(this); + ForkJoinPool p; + if ((p = pool) != null) // else already run + p.scheduleDelayedTask(this); } - // ForkJoinTask methods + // InterruptibleTask methods public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } + final Object adaptee() { return (runnable != null) ? runnable : callable; } - // InterruptibleTask methods final T compute() throws Exception { Callable c; Runnable r; T res = null; @@ -512,16 +515,18 @@ final boolean postExec() { // possibly resubmit } trySetCancelled(); // pool is shutdown } + pool = null; // reduce memory retention + runnable = null; + callable = null; return true; } public final boolean cancel(boolean mayInterruptIfRunning) { - int s; boolean isCancelled; + int s; Thread t; ForkJoinPool p; DelayScheduler ds; if ((s = trySetCancelled()) < 0) - isCancelled = ((s & (ABNORMAL | THROWN)) == ABNORMAL); - else { - Thread t; ForkJoinPool p; DelayScheduler ds; - isCancelled = true; + return ((s & (ABNORMAL | THROWN)) == ABNORMAL); + if ((p = pool) != null) { // not already disabled + pool = null; if ((t = runner) != null) { if (mayInterruptIfRunning) { try { @@ -530,14 +535,17 @@ public final boolean cancel(boolean mayInterruptIfRunning) { } } } - else if (heapIndex >= 0 && nextPending == null && - (p = pool) != null && (ds = p.delayScheduler) != null) - ds.pend(this); // for heap cleanup + else { + runnable = null; + callable = null; + if (heapIndex >= 0 && nextPending == null && + (ds = p.delayScheduler) != null) + ds.pend(this); // for heap cleanup + } } - return isCancelled; + return true; } - final Object adaptee() { return (runnable != null) ? runnable : callable; } // ScheduledFuture methods public final long getDelay(TimeUnit unit) { diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 3ceba291aeb27..5dee283e0e59f 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -3437,7 +3437,7 @@ public T invokeAny(Collection> tasks, // Support for delayed tasks /** - * Creates and starts Delayscheduler + * Creates and starts DelayScheduler */ private DelayScheduler startDelayScheduler() { DelayScheduler ds; @@ -3604,6 +3604,15 @@ public ScheduledFuture schedule(Callable callable, *

    If any execution of this task takes longer than its period, then * subsequent executions may start late, but will not concurrently * execute. + * @param command the task to execute + * @param initialDelay the time to delay first execution + * @param period the period between successive executions + * @param unit the time unit of the initialDelay and period parameters + * @return a ForkJoinTask implementing the ScheduledFuture + * interface. The future's {@link Future#get() get()} + * method will never return normally, and will throw an + * exception upon task cancellation or abnormal + * termination of a task execution. * @throws RejectedExecutionException if the pool is shutdown or * submission encounters resource exhaustion. * @throws NullPointerException if command or unit is null @@ -3644,6 +3653,16 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, * Subsequent executions are suppressed. Subsequent calls to * {@link Future#isDone isDone()} on the returned future will * return {@code true}. + * @param command the task to execute + * @param initialDelay the time to delay first execution + * @param delay the delay between the termination of one + * execution and the commencement of the next + * @param unit the time unit of the initialDelay and delay parameters + * @return a ForkJoinTask implementing the ScheduledFuture + * interface. The future's {@link Future#get() get()} + * method will never return normally, and will throw an + * exception upon task cancellation or abnormal + * termination of a task execution. * @throws RejectedExecutionException if the pool is shutdown or * submission encounters resource exhaustion. * @throws NullPointerException if command or unit is null @@ -3667,11 +3686,13 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, * Body of a ScheduledForkJoinTask serving to cancel another task on timeout */ static final class CancelAction implements Runnable { - Future task; // set after construction + Future task; // set after construction; nulled after use public void run() { Future t; - if ((t = task) != null) + if ((t = task) != null) { + task = null; t.cancel(true); + } } } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 7e09505d7d4d7..35a35e63ccb1e 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1654,7 +1654,7 @@ public final boolean exec() { } return postExec(); } - boolean postExec() { // returns completion status to doExec + boolean postExec() { // cleanup and return completion status to doExec return true; } public boolean cancel(boolean mayInterruptIfRunning) { @@ -1865,8 +1865,8 @@ public final void setRawResult(Void v) { } */ @SuppressWarnings("serial") // Conditionally serializable static final class CallableWithCanceller extends InterruptibleTask { - final Callable callable; - final ForkJoinTask canceller; + Callable callable; // nulled out after use + ForkJoinTask canceller; T result; CallableWithCanceller(Callable callable, ForkJoinTask canceller) { @@ -1875,16 +1875,23 @@ static final class CallableWithCanceller extends InterruptibleTask { } public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } + final Object adaptee() { return callable; } final T compute() throws Exception { - try { - return callable.call(); - } finally { - ForkJoinTask t; // cancel the canceller - if ((t = canceller) != null) + Callable c; + return ((c = callable) != null) ? c.call() : null; + } + final boolean postExec() { // cancel canceller + ForkJoinTask t; + callable = null; + if ((t = canceller) != null) { + canceller = null; + try { t.cancel(false); + } catch (Error | RuntimeException ex) { + } } + return true; } - final Object adaptee() { return callable; } } } diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index ddfaa65cf3cdb..b1bca5cdeabeb 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -590,4 +590,23 @@ public Boolean call() throws Exception { assertFalse(task.isCancelled()); } + /** + * A delayed task completes (possibly abnormally) if shutdown after + * calling cancelDelayedTasksOnShutdown() + */ + public void testCancelDelayedTasksOnShutdown() throws Exception { + final ForkJoinPool p = new ForkJoinPool(2); + p.cancelDelayedTasksOnShutdown(); + try (PoolCleaner cleaner = cleaner(p)) { + Callable task = new CheckedCallable<>() { + public Boolean realCall() { + return Boolean.TRUE; + }}; + Future f = p.schedule(task, LONGER_DELAY_MS, MILLISECONDS); + p.shutdown(); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(f.isDone()); + } + } + } From 753d0e0e5b77fd091deaf0bda86a46d3c8577ff4 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Mon, 17 Feb 2025 07:55:53 -0500 Subject: [PATCH 28/40] Add optional SubmitWithTimeout action --- .../java/util/concurrent/DelayScheduler.java | 27 +++----- .../java/util/concurrent/ForkJoinPool.java | 65 +++++++++++-------- .../java/util/concurrent/ForkJoinTask.java | 46 +++++++------ .../concurrent/tck/ForkJoinPool20Test.java | 4 +- 4 files changed, 75 insertions(+), 67 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index f6f6d726e0f81..d9883253cf290 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -69,7 +69,7 @@ final class DelayScheduler extends Thread { * 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 from of contention but + * 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 @@ -226,7 +226,7 @@ public final void run() { */ private void loop(ForkJoinPool p) { p.onDelaySchedulerStart(); - ScheduledForkJoinTask[] h = // initial heap array + ScheduledForkJoinTask[] h = // heap array new ScheduledForkJoinTask[INITIAL_HEAP_CAPACITY]; int cap = h.length, n = 0, prevRunStatus = 0; // n is heap size for (;;) { // loop until stopped @@ -525,23 +525,14 @@ public final boolean cancel(boolean mayInterruptIfRunning) { int s; Thread t; ForkJoinPool p; DelayScheduler ds; if ((s = trySetCancelled()) < 0) return ((s & (ABNORMAL | THROWN)) == ABNORMAL); - if ((p = pool) != null) { // not already disabled + if ((p = pool) != null && + !interruptIfRunning(mayInterruptIfRunning)) { pool = null; - if ((t = runner) != null) { - if (mayInterruptIfRunning) { - try { - t.interrupt(); - } catch (Throwable ignore) { - } - } - } - else { - runnable = null; - callable = null; - if (heapIndex >= 0 && nextPending == null && - (ds = p.delayScheduler) != null) - ds.pend(this); // for heap cleanup - } + runnable = null; + callable = null; + if (heapIndex >= 0 && nextPending == null && + (ds = p.delayScheduler) != null) + ds.pend(this); // for heap cleanup } return true; } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 5dee283e0e59f..5754303d84a97 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -43,6 +43,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.LockSupport; @@ -3683,37 +3684,48 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, } /** - * Body of a ScheduledForkJoinTask serving to cancel another task on timeout + * Body of a task performed on timeout of another task */ - static final class CancelAction implements Runnable { - Future task; // set after construction; nulled after use + static final class TimeoutAction implements Runnable { + // set after construction, nulled after use + ForkJoinTask.CallableWithTimeout task; + Consumer> action; + TimeoutAction(Consumer> action) { + this.action = action; + } public void run() { - Future t; - if ((t = task) != null) { - task = null; - t.cancel(true); + ForkJoinTask.CallableWithTimeout t = task; + Consumer> a = action; + task = null; + action = null; + if (t != null && t.status >= 0) { + if (a == null) + t.cancel(true); + else { + a.accept(t); + t.interruptIfRunning(true); + } } } } /** - * Submits a task executing the given function, cancelling it (via - * {@code cancel(true)}) if not completed within the given timeout - * period. If you would like to use some other value in the event - * of cancellation and/or other errors, you could use a construction - * such as the following for a submitted {@code task}. - * - *

     {@code
    -     * T result;
    -     * try {
    -     *  result = task.get();
    -     * ) catch (Error | RuntimeException ex) {
    -     *    result = replacementValue;
    -     * }}}
    + * Submits a task executing the given function, cancelling or + * performing a given timeoutAction if not completed within the + * given timeout period. If the optional {@code timeoutAction} is + * null, the task is cancelled (via {@code + * cancel(true)}. Otherwise, the action is applied and the task is + * interrupted if running. Actions may include {@link + * ForkJoinTask#complete} to set a replacement value or {@link + * ForkJoinTask#completeExceptionally} to throw a {@link + * TimeoutException} or related exception. * * @param callable the function to execute * @param the type of the callable's result * @param timeout the time to wait before cancelling if not completed + * @param timeoutAction if nonnull, an action to perform on + * timeout, otherwise the default action is to cancel using {@code + * cancel(true)}. * @param unit the time unit of the timeout parameter * @return a Future that can be used to extract result or cancel * @throws RejectedExecutionException if the task cannot be @@ -3722,16 +3734,17 @@ public void run() { * @since 25 */ public ForkJoinTask submitWithTimeout(Callable callable, - long timeout, TimeUnit unit) { - ForkJoinTask.CallableWithCanceller task; CancelAction onTimeout; + long timeout, TimeUnit unit, + Consumer> timeoutAction) { + ForkJoinTask.CallableWithTimeout task; TimeoutAction onTimeout; Objects.requireNonNull(callable); - ScheduledForkJoinTask canceller = + ScheduledForkJoinTask timeoutTask = new ScheduledForkJoinTask( unit.toNanos(timeout), 0L, true, - onTimeout = new CancelAction(), null, this); + onTimeout = new TimeoutAction(timeoutAction), null, this); onTimeout.task = task = - new ForkJoinTask.CallableWithCanceller(callable, canceller); - scheduleDelayedTask(canceller); + new ForkJoinTask.CallableWithTimeout(callable, timeoutTask); + scheduleDelayedTask(timeoutTask); poolSubmit(true, task); return task; } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 35a35e63ccb1e..6276e5c5e0323 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1657,20 +1657,24 @@ public final boolean exec() { boolean postExec() { // cleanup and return completion status to doExec return true; } + final boolean interruptIfRunning(boolean enabled) { + Thread t; + if ((t = runner) == null) // return false if not running + return false; + if (enabled) { + try { + t.interrupt(); + } catch (Throwable ignore) { + } + } + return true; + } public boolean cancel(boolean mayInterruptIfRunning) { - int s; boolean isCancelled; Thread t; + int s; if ((s = trySetCancelled()) < 0) - isCancelled = ((s & (ABNORMAL | THROWN)) == ABNORMAL); - else { - isCancelled = true; - if (mayInterruptIfRunning && (t = runner) != null) { - try { - t.interrupt(); - } catch (Throwable ignore) { - } - } - } - return isCancelled; + return ((s & (ABNORMAL | THROWN)) == ABNORMAL); + interruptIfRunning(mayInterruptIfRunning); + return true; } public final void run() { quietlyInvoke(); } Object adaptee() { return null; } // for printing and diagnostics @@ -1861,17 +1865,17 @@ public final void setRawResult(Void v) { } } /** - * Adapter for Callable-based interruptible tasks with timeouts. + * Adapter for Callable-based interruptible tasks with timeout actions. */ @SuppressWarnings("serial") // Conditionally serializable - static final class CallableWithCanceller extends InterruptibleTask { + static final class CallableWithTimeout extends InterruptibleTask { Callable callable; // nulled out after use - ForkJoinTask canceller; + ForkJoinTask timeoutAction; T result; - CallableWithCanceller(Callable callable, - ForkJoinTask canceller) { + CallableWithTimeout(Callable callable, + ForkJoinTask timeoutAction) { this.callable = callable; - this.canceller = canceller; + this.timeoutAction = timeoutAction; } public final T getRawResult() { return result; } public final void setRawResult(T v) { result = v; } @@ -1880,11 +1884,11 @@ final T compute() throws Exception { Callable c; return ((c = callable) != null) ? c.call() : null; } - final boolean postExec() { // cancel canceller + final boolean postExec() { // cancel timeout action ForkJoinTask t; callable = null; - if ((t = canceller) != null) { - canceller = null; + if ((t = timeoutAction) != null) { + timeoutAction = null; try { t.cancel(false); } catch (Error | RuntimeException ex) { diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index b1bca5cdeabeb..6139725308724 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -573,7 +573,7 @@ public void testSubmitWithTimeoutCancels() throws InterruptedException { Callable c = new Callable() { public Boolean call() throws Exception { Thread.sleep(LONGER_DELAY_MS); return Boolean.TRUE; }}; - ForkJoinTask task = p.submitWithTimeout(c, 1, NANOSECONDS); + ForkJoinTask task = p.submitWithTimeout(c, 1, NANOSECONDS, null); Thread.sleep(timeoutMillis()); assertTrue(task.isCancelled()); } @@ -585,7 +585,7 @@ public void testSubmitWithTimeout_NoTimeout() throws InterruptedException { Callable c = new Callable() { public Boolean call() throws Exception { return Boolean.TRUE; }}; - ForkJoinTask task = p.submitWithTimeout(c, LONGER_DELAY_MS, MILLISECONDS); + ForkJoinTask task = p.submitWithTimeout(c, LONGER_DELAY_MS, MILLISECONDS, null); Thread.sleep(timeoutMillis()); assertFalse(task.isCancelled()); } From 53516e9de35a087ca8a0fb2dccb8ad6b7a01851f Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Wed, 19 Feb 2025 10:57:46 -0500 Subject: [PATCH 29/40] Misc minor improvements and renamings for clarity --- .../util/concurrent/CompletableFuture.java | 58 ++++++------ .../java/util/concurrent/DelayScheduler.java | 60 ++++++------- .../java/util/concurrent/ForkJoinPool.java | 88 ++++++++++--------- .../concurrent/tck/ForkJoinPool20Test.java | 41 +++++++++ 4 files changed, 142 insertions(+), 105 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java index 153f8530c1a90..b9ac2e020275d 100644 --- a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java +++ b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java @@ -2814,6 +2814,34 @@ public CompletableFuture completeOnTimeout(T value, long timeout, return this; } + /** Action to complete (possibly exceptionally) on timeout */ + static final class Timeout implements Runnable { + final CompletableFuture f; + final U value; + final boolean exceptional; + Timeout(CompletableFuture f, U value, boolean exceptional) { + this.f = f; this.value = value; this.exceptional = exceptional; + } + public void run() { + if (f != null && !f.isDone()) { + if (exceptional) + f.completeExceptionally(new TimeoutException()); + else + f.complete(value); + } + } + } + + /** Action to cancel unneeded timeouts */ + static final class Canceller implements BiConsumer { + final Future f; + Canceller(Future f) { this.f = f; } + public void accept(Object ignore, Throwable ex) { + if (f != null) + f.cancel(false); + } + } + /** * Schedules a timeout action, as well as whenComplete handling to * cancel the action if not needed. @@ -2822,7 +2850,7 @@ private void arrangeTimeout(long nanoDelay, Timeout onTimeout) { ForkJoinPool e = ASYNC_POOL; if (result == null) { ScheduledForkJoinTask t = new ScheduledForkJoinTask( - nanoDelay, 0L, false, onTimeout, null, e); + nanoDelay, 0L, true, onTimeout, null, e); whenComplete(new Canceller(t)); e.scheduleDelayedTask(t); } @@ -2932,34 +2960,6 @@ static final class TaskSubmitter implements Runnable { public void run() { executor.execute(action); } } - /** Action to complete (possibly exceptionally) on timeout */ - static final class Timeout implements Runnable { - final CompletableFuture f; - final U value; - final boolean exceptional; - Timeout(CompletableFuture f, U value, boolean exceptional) { - this.f = f; this.value = value; this.exceptional = exceptional; - } - public void run() { - if (f != null && !f.isDone()) { - if (exceptional) - f.completeExceptionally(new TimeoutException()); - else - f.complete(value); - } - } - } - - /** Action to cancel unneeded timeouts */ - static final class Canceller implements BiConsumer { - final Future f; - Canceller(Future f) { this.f = f; } - public void accept(Object ignore, Throwable ex) { - if (f != null) - f.cancel(false); - } - } - /** * A subclass that just throws UOE for most non-CompletionStage methods. */ diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index d9883253cf290..123704a4d0f92 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -73,9 +73,9 @@ final class DelayScheduler extends Thread { * it doesn't seem worthwhile even under heavy loads. * * The implementation relies on the scheduler being a non-virtual - * 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 + * 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 @@ -118,8 +118,8 @@ final class DelayScheduler extends Thread { volatile int active; // 0: inactive, -1: stopped, +1: running int restingSize; // written only before parking volatile int cancelDelayedTasksOnShutdown; // policy control - // reduce trailing false sharing - int pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7, pad8, pad9, padA; + 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 @@ -153,7 +153,7 @@ static final long now() { * Ensures the scheduler is not parked unless stopped. * Returns negative if already stopped */ - final int ensureActive() { + final int signal() { int state; if ((state = active) == 0 && U.getAndBitwiseOrInt(this, ACTIVE, 1) == 0) U.unpark(this); @@ -171,8 +171,8 @@ final void pend(ScheduledForkJoinTask task) { f != (f = (ScheduledForkJoinTask) U.compareAndExchangeReference( this, PENDING, task.nextPending = f, task))); - ensureActive(); } + signal(); } /** @@ -183,9 +183,9 @@ final boolean canShutDown() { } /** - * Returns an approximate number of elements in heap + * Returns the number of elements in heap when last idle */ - final int approximateSize() { + final int lastStableSize() { return (active < 0) ? 0 : restingSize; } @@ -194,7 +194,7 @@ final int approximateSize() { */ final void cancelDelayedTasksOnShutdown() { cancelDelayedTasksOnShutdown = 1; - ensureActive(); + signal(); } /** @@ -211,7 +211,7 @@ public final void run() { } finally { restingSize = 0; active = -1; - p.tryStopIfEnabled(); + p.tryStopIfShutdown(); } } } @@ -251,8 +251,7 @@ else if (stat >= 0) { t.trySetCancelled(); else { // add and sift up ScheduledForkJoinTask parent; - ScheduledForkJoinTask[] nh; - int k = n++, pk, nc; + int k = n++, pk, newCap; while (k > 0 && (parent = h[pk = (k - 1) >>> 2]) != null && (parent.when > d)) { @@ -262,17 +261,23 @@ else if (stat >= 0) { } t.heapIndex = k; h[k] = t; - if (n >= cap && (nh = growHeap(h)) != null && - (nc = nh.length) > cap) { - cap = nc; // else keep using old array - h = nh; + 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; // else keep using old array + h = a; + } } } } } while ((t = next) != null); } - if ((runStatus = p.delaySchedulerRunStatus()) != 0) { + if ((runStatus = p.shutdownStatus()) != 0) { if ((n = tryStop(p, h, n, runStatus, prevRunStatus)) < 0) break; prevRunStatus = runStatus; @@ -309,21 +314,6 @@ else if (stat >= 0) { } } - /** - * Tries to reallocate the heap array, returning null on failure - */ - private static ScheduledForkJoinTask[] growHeap(ScheduledForkJoinTask[] h) { - int cap, newCap; - ScheduledForkJoinTask[] nh = null; - if (h != null && (cap = h.length) < (newCap = cap << 1)) { - try { - nh = Arrays.copyOf(h, newCap); - } catch (Error | RuntimeException ex) { - } - } - return nh; - } - /** * Replaces removed heap element at index k, along with other * cancelled nodes found while doing so. @@ -412,7 +402,7 @@ else if (prevRunStatus == 0 && h != null && h.length >= n) { } } } - if (n > 0 || p == null || !p.tryStopIfEnabled()) + if (n > 0 || p == null || !p.tryStopIfShutdown()) return n; // check for quiescent shutdown } if (n > 0) @@ -504,7 +494,7 @@ final boolean postExec() { // possibly resubmit if ((d = nextDelay) != 0L && // is periodic status >= 0 && // not abnormally completed (p = pool) != null && (ds = p.delayScheduler) != null) { - if (p.delaySchedulerRunStatus() == 0) { + if (p.shutdownStatus() == 0) { heapIndex = -1; if (d < 0L) when = DelayScheduler.now() - d; diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 5754303d84a97..f6d819ab7a213 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -161,7 +161,8 @@ * setting the following {@linkplain System#getProperty system properties}: *
      *
    • {@systemProperty java.util.concurrent.ForkJoinPool.common.parallelism} - * - the parallelism level, a non-negative integer + * - the parallelism level, a non-negative integer. Usage is discouraged. + * Use {@link #setParallelism} instead. *
    • {@systemProperty java.util.concurrent.ForkJoinPool.common.threadFactory} * - the class name of a {@link ForkJoinWorkerThreadFactory}. * The {@linkplain ClassLoader#getSystemClassLoader() system class loader} @@ -179,10 +180,11 @@ * {@linkplain Thread#getContextClassLoader() thread context class loader}. * * Upon any error in establishing these settings, default parameters - * are used. It is possible to disable or limit the use of threads in - * the common pool by setting the parallelism property to zero, and/or - * using a factory that may return {@code null}. However doing so may - * cause unjoined tasks to never be executed. + * are used. It is possible to disable use of threads by using a + * factory that may return {@code null}, in which case some tasks may + * never execute. It is also possible but strongly discouraged to set + * the parallelism property to zero, which may be internally + * overridden in the presence of intrinsically async tasks. * * @implNote This implementation restricts the maximum number of * running threads to 32767. Attempts to create pools with greater @@ -854,8 +856,11 @@ public class ForkJoinPool extends AbstractExecutorService * including those that retry helping steps until we are sure that * none apply if there are no workers. To deal with conflicting * requirements, uses of the commonPool that require async because - * caller-runs does not apply, ensure at least one thread (method - * asyncCommonPool) before proceeding. + * caller-runs need not apply, ensure threads are enabled (by + * setting parallelism) via method asyncCommonPool before + * proceeding. (In principle, these need to ensure at least one + * worker, but due to other backward compatibility contraints, + * ensure two.) * * As a more appropriate default in managed environments, unless * overridden by system properties, we use workers of subclass @@ -917,16 +922,17 @@ public class ForkJoinPool extends AbstractExecutorService * methods (via startDelayScheduler, with callback * onDelaySchedulerStart). The scheduler operates independently in * its own thread, relaying tasks to the pool to execute as they - * become ready (see method - * executeReadyScheculedTask). The only other interactions - * with the delayScheduler are to control shutdown and maintain - * shutdown-related policies in methods quiescent() and - * tryTerminate(). In particular, to conform to policies, - * shutdown-related processing must deal with cases in which tasks - * are submitted before shutdown, but not ready until afterwards, - * in which case they must bypass some screening to be allowed to - * run. Conversely, the DelayScheduler interacts with the pool - * only to check runState status and complete termination. + * become ready (see method executeReadyScheduledTask). The only + * other interactions with the delayScheduler are to control + * shutdown and maintain shutdown-related policies in methods + * quiescent() and tryTerminate(). In particular, to conform to + * policies, shutdown-related processing must deal with cases in + * which tasks are submitted before shutdown, but not ready until + * afterwards, in which case they must bypass some screening to be + * allowed to run. Conversely, the DelayScheduler interacts with + * the pool only to check runState status and complete + * termination, using only methods shutdownStatus and + * tryStopIfShutdown. * * Memory placement * ================ @@ -1737,6 +1743,14 @@ static boolean poolIsStopping(ForkJoinPool p) { // Used by ForkJoinTask return p != null && (p.runState & STOP) != 0L; } + /** + * Returns STOP and SHUTDOWN status (zero if neither), masking or + * truncating out other bits. + */ + final int shutdownStatus() { + return (int)(runState & (SHUTDOWN | STOP)); + } + // Creating, registering, and deregistering workers /** @@ -1860,7 +1874,7 @@ final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) { unlockRunState(); } } - if (!tryStopIfEnabled() && phase != 0 && w != null && w.source != DROPPED) { + if (!tryStopIfShutdown() && phase != 0 && w != null && w.source != DROPPED) { signalWork(); // possibly replace w.cancelTasks(); // clean queue } @@ -2760,14 +2774,14 @@ else if ((isShutdown = (e & SHUTDOWN)) != 0L || enable) { if ((quiet = quiescent()) > 0) now = true; else if (quiet == 0 && (ds = delayScheduler) != null) - ds.ensureActive(); + ds.signal(); } if (now) { DelayScheduler ds; releaseWaiters(); if ((ds = delayScheduler) != null) - ds.ensureActive(); + ds.signal(); for (;;) { if (((e = runState) & CLEANED) == 0L) { boolean clean = cleanQueues(); @@ -2778,7 +2792,7 @@ else if (quiet == 0 && (ds = delayScheduler) != null) break; if (ctl != 0L) // else loop if didn't finish cleaning break; - if ((ds = delayScheduler) != null && ds.ensureActive() >= 0) + if ((ds = delayScheduler) != null && ds.signal() >= 0) break; if ((e & CLEANED) != 0L) { e |= TERMINATED; @@ -2799,7 +2813,7 @@ else if (quiet == 0 && (ds = delayScheduler) != null) /** * Tries to stop and possibly terminate if already enabled, return success. */ - final boolean tryStopIfEnabled() { + final boolean tryStopIfShutdown() { return (tryTerminate(false, false) & STOP) != 0L; } @@ -3123,9 +3137,9 @@ public static ForkJoinPool commonPool() { * Package-private access to commonPool overriding zero parallelism */ static ForkJoinPool asyncCommonPool() { - ForkJoinPool cp; - if ((cp = common).parallelism == 0) - U.compareAndSetInt(cp, PARALLELISM, 0, 1); + ForkJoinPool cp; int p; + if ((p = (cp = common).parallelism) < 2) + U.compareAndSetInt(cp, PARALLELISM, p, 2); return cp; } @@ -3477,14 +3491,6 @@ final void onDelaySchedulerStart() { q.unlockPhase(); } - /** - * Returns STOP and SHUTDOWN status for DelayScheduler (zero if - * neither). - */ - final int delaySchedulerRunStatus() { - return (int)(runState & (SHUTDOWN | STOP)); - } - /** * Arranges execution of a ready task from DelayScheduler, or * cancels it on error @@ -3713,19 +3719,19 @@ public void run() { * Submits a task executing the given function, cancelling or * performing a given timeoutAction if not completed within the * given timeout period. If the optional {@code timeoutAction} is - * null, the task is cancelled (via {@code - * cancel(true)}. Otherwise, the action is applied and the task is + * null, the task is cancelled (via {@code cancel(true)}. + * Otherwise, the action is applied and the task may be * interrupted if running. Actions may include {@link * ForkJoinTask#complete} to set a replacement value or {@link - * ForkJoinTask#completeExceptionally} to throw a {@link - * TimeoutException} or related exception. + * ForkJoinTask#completeExceptionally} to throw an appropriate + * exception. * * @param callable the function to execute * @param the type of the callable's result * @param timeout the time to wait before cancelling if not completed * @param timeoutAction if nonnull, an action to perform on - * timeout, otherwise the default action is to cancel using {@code - * cancel(true)}. + * timeout, otherwise the default action is to cancel using + * {@code cancel(true)}. * @param unit the time unit of the timeout parameter * @return a Future that can be used to extract result or cancel * @throws RejectedExecutionException if the task cannot be @@ -3951,7 +3957,7 @@ public int getQueuedSubmissionCount() { */ public int getDelayedTaskCount() { DelayScheduler ds; - return ((ds = delayScheduler) == null ? 0 : ds.approximateSize()); + return ((ds = delayScheduler) == null ? 0 : ds.lastStableSize()); } /** @@ -4038,7 +4044,7 @@ public String toString() { } } String delayed = ((ds = delayScheduler) == null ? "" : - ", delayed = " + ds.approximateSize()); + ", delayed = " + ds.lastStableSize()); int pc = parallelism; long c = ctl; int tc = (short)(c >>> TC_SHIFT); diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index 6139725308724..5e83ef4bd8356 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -577,6 +577,47 @@ public Boolean call() throws Exception { Thread.sleep(timeoutMillis()); assertTrue(task.isCancelled()); } + + static final class SubmitWithTimeoutException extends RuntimeException {} + + /** + * submitWithTimeout using complete completes after timeout + */ + public void testSubmitWithCompleterTimeoutCompletes() throws InterruptedException { + final ForkJoinPool p = ForkJoinPool.commonPool(); + Callable c = new Callable() { + public Item call() throws Exception { + Thread.sleep(LONGER_DELAY_MS); return one; }}; + ForkJoinTask task = p.submitWithTimeout( + c, 1, NANOSECONDS, + (ForkJoinTask t) -> + t.complete(two)); + Thread.sleep(timeoutMillis()); + assertEquals(task.join(), two); + } + + /** + * submitWithTimeout using completeExceptionally throws after timeout + */ + public void testSubmitWithTimeoutThrows() throws InterruptedException { + final ForkJoinPool p = ForkJoinPool.commonPool(); + Callable c = new Callable() { + public Boolean call() throws Exception { + Thread.sleep(LONGER_DELAY_MS); return Boolean.TRUE; }}; + ForkJoinTask task = p.submitWithTimeout( + c, 1, NANOSECONDS, + (ForkJoinTask t) -> + t.completeExceptionally(new SubmitWithTimeoutException())); + Thread.sleep(timeoutMillis()); + try { + task.join(); + shouldThrow(); + } + catch (Exception ex) { + assertTrue(ex instanceof SubmitWithTimeoutException); + } + } + /** * submitWithTimeout doesn't cancel if completed before timeout */ From 16815cc020bc71167b63a659cd755ed11aab8c6b Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Fri, 21 Feb 2025 08:12:42 -0500 Subject: [PATCH 30/40] Address feedback --- .../java/util/concurrent/DelayScheduler.java | 36 +++++++------- .../java/util/concurrent/ForkJoinPool.java | 49 ++++++++++--------- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index 123704a4d0f92..624d0614e1740 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -55,7 +55,7 @@ final class DelayScheduler extends Thread { * (known, non-blocking) action is to cancel, unblock or * differently relay another async task. If processing encounters * resource failures (possible when growing heap or ForkJoinPool - * WorkQUeue arrays), tasks are cancelled. + * WorkQueue arrays), tasks are cancelled. * * To reduce memory contention, the heap is maintained solely via * local variables in method loop() (forcing noticeable code @@ -284,23 +284,25 @@ else if (stat >= 0) { } long parkTime = 0L; // zero for untimed park - while (n > 0 && h.length > 0) { // submit ready tasks - 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) { - if (f.isImmediate) - f.doExec(); - else - p.executeReadyScheduledTask(f); + if (n > 0 && h.length > 0) { // submit ready 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) { + if (f.isImmediate) + f.doExec(); + else + p.executeReadyScheduledTask(f); + } } - } - n = replace(h, 0, n); + } while ((n = replace(h, 0, n)) > 0); } if (pending == null) { diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index f6d819ab7a213..92e75f7323fa7 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -139,23 +139,25 @@ * *

      Additionally, this class supports {@link * ScheduledExecutorService} methods to delay or periodically execute - * tasks, as well as method {#link #submitWithTimeout} to cancel tasks + * tasks, as well as method {@link #submitWithTimeout} to cancel tasks * that take too long. The scheduled functions or actions may create - * and invoke other {@linkplain ForkJoinTask ForkJoinTasks}. The - * schedule methods return {@linkplain ForkJoinTask ForkJoinTasks} - * that implement the {@link ScheduledFuture} interface. Resource - * exhaustion encountered after initial submission results in task - * cancellation. When time-based methods are used, shutdown policies - * are based on the default policies of class {@link - * ScheduledThreadPoolExecutor}: upon {@link #shutdown}, existing - * periodic tasks will not re-execute, and the pool terminates when - * quiescent and existing delayed tasks complete. Method {@link - * #cancelDelayedTasksOnShutdown} may be used to disable all delayed - * tasks upon shutdown, and method {@link #shutdownNow} may be used to - * instead unconditionally initiate pool termination. Monitoring - * methods such as {@link getQueuedTaskCount} do not include scheduled - * tasks that are not yet ready to execute, whcih are reported - * separately by method {@link getDelayedTaskCount}. + * and invoke other {@linkplain ForkJoinTask ForkJoinTasks}. Delayed + * actions become enabled and behave as ordinary submitted + * tasks when their delays elapse. schedule methods return + * {@linkplain ForkJoinTask ForkJoinTasks} that implement the {@link + * ScheduledFuture} interface. Resource exhaustion encountered after + * initial submission results in task cancellation. When time-based + * methods are used, shutdown policies are based on the default + * policies of class {@link ScheduledThreadPoolExecutor}: upon {@link + * #shutdown}, existing periodic tasks will not re-execute, and the + * pool terminates when quiescent and existing delayed tasks + * complete. Method {@link #cancelDelayedTasksOnShutdown} may be used + * to disable all delayed tasks upon shutdown, and method {@link + * #shutdownNow} may be used to instead unconditionally initiate pool + * termination. Monitoring methods such as {@link getQueuedTaskCount} + * do not include scheduled tasks that are not yet ready to execute, + * whcih are reported separately by method {@link + * getDelayedTaskCount}. * *

      The parameters used to construct the common pool may be controlled by * setting the following {@linkplain System#getProperty system properties}: @@ -3716,15 +3718,16 @@ public void run() { } /** - * Submits a task executing the given function, cancelling or - * performing a given timeoutAction if not completed within the - * given timeout period. If the optional {@code timeoutAction} is - * null, the task is cancelled (via {@code cancel(true)}. - * Otherwise, the action is applied and the task may be - * interrupted if running. Actions may include {@link + * Submits a task executing the given function, cancelling the + * task or performing a given timeoutAction if not completed + * within the given timeout period. If the optional {@code + * timeoutAction} is null, the task is cancelled (via {@code + * cancel(true)}. Otherwise, the action is applied and the task + * may be interrupted if running. Actions may include {@link * ForkJoinTask#complete} to set a replacement value or {@link * ForkJoinTask#completeExceptionally} to throw an appropriate - * exception. + * exception. Note that these will only succeed if the task has + * not already completed when the timeoutAction executes. * * @param callable the function to execute * @param the type of the callable's result From 84eaab0501080ddeb4fa7a4a8fd6696f4d2cdabd Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sat, 22 Feb 2025 09:24:49 -0500 Subject: [PATCH 31/40] Address review comments; ensure new internal methods can't clash with subclasses --- .../util/concurrent/CompletableFuture.java | 9 +- .../java/util/concurrent/DelayScheduler.java | 40 ++++---- .../java/util/concurrent/ForkJoinPool.java | 92 ++++++++++--------- .../java/util/concurrent/ForkJoinTask.java | 1 - ...tableFutureOrTimeoutExceptionallyTest.java | 4 +- 5 files changed, 76 insertions(+), 70 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java index b9ac2e020275d..7503c154ddbea 100644 --- a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java +++ b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java @@ -2790,7 +2790,7 @@ public CompletableFuture completeAsync(Supplier supplier) { * @since 9 */ public CompletableFuture orTimeout(long timeout, TimeUnit unit) { - arrangeTimeout(unit.toNanos(timeout), + arrangeTimeout(unit.toNanos(timeout), // Implicit null-check of unit new Timeout(this, null, true)); return this; } @@ -2837,7 +2837,7 @@ static final class Canceller implements BiConsumer { final Future f; Canceller(Future f) { this.f = f; } public void accept(Object ignore, Throwable ex) { - if (f != null) + if (f != null) // currently never null f.cancel(false); } } @@ -2871,7 +2871,7 @@ private void arrangeTimeout(long nanoDelay, Timeout onTimeout) { */ public static Executor delayedExecutor(long delay, TimeUnit unit, Executor executor) { - return new DelayedExecutor(unit.toNanos(delay), + return new DelayedExecutor(unit.toNanos(delay), // implicit null check Objects.requireNonNull(executor)); } @@ -2888,7 +2888,8 @@ public static Executor delayedExecutor(long delay, TimeUnit unit, * @since 9 */ public static Executor delayedExecutor(long delay, TimeUnit unit) { - return new DelayedExecutor(unit.toNanos(delay), ASYNC_POOL); + return new DelayedExecutor(unit.toNanos(delay), // implicit null check + ASYNC_POOL); } /** diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index 624d0614e1740..acd69cedc7417 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -49,13 +49,15 @@ 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 ready, tasks - * are relayed to the pool, or run directly if task.isImmediate. - * Immediate mode is designed for internal jdk usages in which the - * (known, non-blocking) action is to cancel, unblock or - * differently relay another async task. If processing encounters - * resource failures (possible when growing heap or ForkJoinPool - * WorkQueue arrays), tasks are cancelled. + * 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 (known, 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 + * actually being async). 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 @@ -81,7 +83,7 @@ final class DelayScheduler extends Thread { * 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 - * tryTerminate checks as well as to suppress reactivation while + * termination checks as well as to suppress reactivation while * terminating. * * The implementation is designed to accommodate usages in which @@ -161,7 +163,7 @@ final int signal() { } /** - * Inserts the task (if non-null) to pending queue, to add, + * Inserts the task (if nonnull) to pending queue, to add, * remove, or ignore depending on task status when processed. */ final void pend(ScheduledForkJoinTask task) { @@ -171,8 +173,8 @@ final void pend(ScheduledForkJoinTask task) { f != (f = (ScheduledForkJoinTask) U.compareAndExchangeReference( this, PENDING, task.nextPending = f, task))); + signal(); } - signal(); } /** @@ -211,7 +213,7 @@ public final void run() { } finally { restingSize = 0; active = -1; - p.tryStopIfShutdown(); + p.tryStopIfShutdown(this); } } } @@ -220,12 +222,12 @@ public final void run() { * After initialization, repeatedly: * 1. Process pending tasks in batches, to add or remove from heap * 2. Check for shutdown, either exiting or preparing for shutdown when empty - * 3. Trigger all ready tasks by externally submitting them to pool + * 3. Trigger all enabled tasks by externally submitting them to pool * 4. If active, set tentatively inactive, * else park until next trigger time, or indefinitely if none */ private void loop(ForkJoinPool p) { - p.onDelaySchedulerStart(); + p.onDelaySchedulerStart(this); ScheduledForkJoinTask[] h = // heap array new ScheduledForkJoinTask[INITIAL_HEAP_CAPACITY]; int cap = h.length, n = 0, prevRunStatus = 0; // n is heap size @@ -277,14 +279,14 @@ else if (stat >= 0) { } while ((t = next) != null); } - if ((runStatus = p.shutdownStatus()) != 0) { + if ((runStatus = p.shutdownStatus(this)) != 0) { if ((n = tryStop(p, h, n, runStatus, prevRunStatus)) < 0) break; prevRunStatus = runStatus; } long parkTime = 0L; // zero for untimed park - if (n > 0 && h.length > 0) { // submit ready tasks + if (n > 0 && h.length > 0) { // submit enabled tasks long now = now(); do { ScheduledForkJoinTask f; int stat; @@ -299,7 +301,7 @@ else if (stat >= 0) { if (f.isImmediate) f.doExec(); else - p.executeReadyScheduledTask(f); + p.executeEnabledScheduledTask(f); } } } while ((n = replace(h, 0, n)) > 0); @@ -404,7 +406,7 @@ else if (prevRunStatus == 0 && h != null && h.length >= n) { } } } - if (n > 0 || p == null || !p.tryStopIfShutdown()) + if (n > 0 || p == null || !p.tryStopIfShutdown(this)) return n; // check for quiescent shutdown } if (n > 0) @@ -496,7 +498,7 @@ final boolean postExec() { // possibly resubmit if ((d = nextDelay) != 0L && // is periodic status >= 0 && // not abnormally completed (p = pool) != null && (ds = p.delayScheduler) != null) { - if (p.shutdownStatus() == 0) { + if (p.shutdownStatus(ds) == 0) { heapIndex = -1; if (d < 0L) when = DelayScheduler.now() - d; @@ -514,7 +516,7 @@ final boolean postExec() { // possibly resubmit } public final boolean cancel(boolean mayInterruptIfRunning) { - int s; Thread t; ForkJoinPool p; DelayScheduler ds; + int s; ForkJoinPool p; DelayScheduler ds; if ((s = trySetCancelled()) < 0) return ((s & (ABNORMAL | THROWN)) == ABNORMAL); if ((p = pool) != null && diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 92e75f7323fa7..299b7f89555fa 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -38,7 +38,6 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -47,7 +46,6 @@ import java.util.function.Predicate; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.LockSupport; -import java.util.concurrent.locks.ReentrantLock; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.JavaUtilConcurrentFJPAccess; import jdk.internal.access.SharedSecrets; @@ -143,7 +141,7 @@ * that take too long. The scheduled functions or actions may create * and invoke other {@linkplain ForkJoinTask ForkJoinTasks}. Delayed * actions become enabled and behave as ordinary submitted - * tasks when their delays elapse. schedule methods return + * tasks when their delays elapse. Scheduling methods return * {@linkplain ForkJoinTask ForkJoinTasks} that implement the {@link * ScheduledFuture} interface. Resource exhaustion encountered after * initial submission results in task cancellation. When time-based @@ -154,10 +152,10 @@ * complete. Method {@link #cancelDelayedTasksOnShutdown} may be used * to disable all delayed tasks upon shutdown, and method {@link * #shutdownNow} may be used to instead unconditionally initiate pool - * termination. Monitoring methods such as {@link getQueuedTaskCount} - * do not include scheduled tasks that are not yet ready to execute, - * whcih are reported separately by method {@link - * getDelayedTaskCount}. + * termination. Monitoring methods such as {@link #getQueuedTaskCount} + * do not include scheduled tasks that are not yet enabled to execute, + * which are reported separately by method {@link + * #getDelayedTaskCount}. * *

      The parameters used to construct the common pool may be controlled by * setting the following {@linkplain System#getProperty system properties}: @@ -184,7 +182,7 @@ * Upon any error in establishing these settings, default parameters * are used. It is possible to disable use of threads by using a * factory that may return {@code null}, in which case some tasks may - * never execute. It is also possible but strongly discouraged to set + * never execute. While possible, it is strongly discouraged to set * the parallelism property to zero, which may be internally * overridden in the presence of intrinsically async tasks. * @@ -923,18 +921,20 @@ public class ForkJoinPool extends AbstractExecutorService * creating and starting a DelayScheduler on first use of these * methods (via startDelayScheduler, with callback * onDelaySchedulerStart). The scheduler operates independently in - * its own thread, relaying tasks to the pool to execute as they - * become ready (see method executeReadyScheduledTask). The only - * other interactions with the delayScheduler are to control - * shutdown and maintain shutdown-related policies in methods - * quiescent() and tryTerminate(). In particular, to conform to - * policies, shutdown-related processing must deal with cases in - * which tasks are submitted before shutdown, but not ready until - * afterwards, in which case they must bypass some screening to be - * allowed to run. Conversely, the DelayScheduler interacts with - * the pool only to check runState status and complete - * termination, using only methods shutdownStatus and - * tryStopIfShutdown. + * its own thread, relaying tasks to the pool to execute when + * their delays elapse (see method executeEnabledScheduledTask). + * The only other interactions with the delayScheduler are to + * control shutdown and maintain shutdown-related policies in + * methods quiescent() and tryTerminate(). In particular, to + * conform to policies, shutdown-related processing must deal with + * cases in which tasks are submitted before shutdown, but not + * ready until afterwards, in which case they must bypass some + * screening to be allowed to run. Conversely, the DelayScheduler + * checks runState status and when enabled, completes termination, + * using only methods shutdownStatus and tryStopIfShutdown. All of + * these methods are final and have signatures referencing + * DelaySchedulers, so cannot conflict with those of any existing + * FJP subclasses. * * Memory placement * ================ @@ -1745,14 +1745,6 @@ static boolean poolIsStopping(ForkJoinPool p) { // Used by ForkJoinTask return p != null && (p.runState & STOP) != 0L; } - /** - * Returns STOP and SHUTDOWN status (zero if neither), masking or - * truncating out other bits. - */ - final int shutdownStatus() { - return (int)(runState & (SHUTDOWN | STOP)); - } - // Creating, registering, and deregistering workers /** @@ -1876,7 +1868,8 @@ final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) { unlockRunState(); } } - if (!tryStopIfShutdown() && phase != 0 && w != null && w.source != DROPPED) { + if ((tryTerminate(false, false) & STOP) == 0L && + phase != 0 && w != null && w.source != DROPPED) { signalWork(); // possibly replace w.cancelTasks(); // clean queue } @@ -2582,7 +2575,8 @@ final ForkJoinTask nextTaskFor(WorkQueue w) { * Finds and locks a WorkQueue for an external submitter, or * throws RejectedExecutionException if shutdown or terminating. * @param r current ThreadLocalRandom.getProbe() value - * @param rejectOnShutdown true if throw RJE when shutdown + * @param rejectOnShutdown true if RejectedExecutionException + * should be thrown when shutdown (else only if terminating) */ private WorkQueue submissionQueue(int r, boolean rejectOnShutdown) { if (r == 0) { @@ -2812,13 +2806,6 @@ else if (quiet == 0 && (ds = delayScheduler) != null) return e; } - /** - * Tries to stop and possibly terminate if already enabled, return success. - */ - final boolean tryStopIfShutdown() { - return (tryTerminate(false, false) & STOP) != 0L; - } - /** * Scans queues in a psuedorandom order based on thread id, * cancelling tasks until empty, or returning early upon @@ -3453,6 +3440,21 @@ public T invokeAny(Collection> tasks, // Support for delayed tasks + /** + * Returns STOP and SHUTDOWN status (zero if neither), masking or + * truncating out other bits. + */ + final int shutdownStatus(DelayScheduler ds) { + return (int)(runState & (SHUTDOWN | STOP)); + } + + /** + * Tries to stop and possibly terminate if already enabled, return success. + */ + final boolean tryStopIfShutdown(DelayScheduler ds) { + return (tryTerminate(false, false) & STOP) != 0L; + } + /** * Creates and starts DelayScheduler */ @@ -3487,17 +3489,17 @@ private DelayScheduler startDelayScheduler() { /** * Callback upon starting DelayScheduler */ - final void onDelaySchedulerStart() { + final void onDelaySchedulerStart(DelayScheduler ds) { WorkQueue q; // set up default submission queue if ((q = submissionQueue(0, false)) != null) q.unlockPhase(); } /** - * Arranges execution of a ready task from DelayScheduler, or - * cancels it on error + * Arranges execution of a ScheduledForkJoinTask whose delay has + * elapsed, or cancels it on error */ - final void executeReadyScheduledTask(ScheduledForkJoinTask task) { + final void executeEnabledScheduledTask(ScheduledForkJoinTask task) { if (task != null) { WorkQueue q; boolean cancel = false; @@ -3523,7 +3525,7 @@ final ScheduledForkJoinTask scheduleDelayedTask(ScheduledForkJoinTask DelayScheduler ds; if (((ds = delayScheduler) == null && (ds = startDelayScheduler()) == null) || - task == null || (runState & SHUTDOWN) != 0L) + (runState & SHUTDOWN) != 0L) throw new RejectedExecutionException(); ds.pend(task); return task; @@ -3531,7 +3533,7 @@ final ScheduledForkJoinTask scheduleDelayedTask(ScheduledForkJoinTask /** * Submits a one-shot task that becomes enabled after the given - * delay, At which point it will execute unless explicitly + * delay. At that point it will execute unless explicitly * cancelled, or fail to execute (eventually reporting * cancellation) when encountering resource exhaustion, or the * pool is {@link #shutdownNow}, or is {@link #shutdown} when @@ -3553,13 +3555,13 @@ public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { Objects.requireNonNull(command); return scheduleDelayedTask( - new ScheduledForkJoinTask( + new ScheduledForkJoinTask( // implicit null check of unit unit.toNanos(delay), 0L, false, command, null, this)); } /** * Submits a value-returning one-shot task that becomes enabled - * after the given delay. At which point it will execute unless + * after the given delay. At that point it will execute unless * explicitly cancelled, or fail to execute (eventually reporting * cancellation) when encountering resource exhaustion, or the * pool is {@link #shutdownNow}, or is {@link #shutdown} when diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 6276e5c5e0323..e761d15de8f19 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -43,7 +43,6 @@ import java.util.RandomAccess; import java.util.concurrent.locks.LockSupport; import jdk.internal.misc.Unsafe; -import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * Abstract base class for tasks that run within a {@link ForkJoinPool}. diff --git a/test/jdk/java/util/concurrent/CompletableFuture/CompletableFutureOrTimeoutExceptionallyTest.java b/test/jdk/java/util/concurrent/CompletableFuture/CompletableFutureOrTimeoutExceptionallyTest.java index 26fcceb66bf6a..ead185a0a1e44 100644 --- a/test/jdk/java/util/concurrent/CompletableFuture/CompletableFutureOrTimeoutExceptionallyTest.java +++ b/test/jdk/java/util/concurrent/CompletableFuture/CompletableFutureOrTimeoutExceptionallyTest.java @@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; class CompletableFutureOrTimeoutExceptionallyTest { // updated February 2025 to adapt to CompletableFuture DelayScheduler changes @@ -45,6 +45,7 @@ class CompletableFutureOrTimeoutExceptionallyTest { @Test void testOrTimeoutWithCompleteExceptionallyDoesNotLeak() throws InterruptedException { ForkJoinPool delayer = ForkJoinPool.commonPool(); + assertEquals(delayer.getDelayedTaskCount(), 0); var future = new CompletableFuture<>().orTimeout(12, TimeUnit.HOURS); future.completeExceptionally(new RuntimeException("This is fine")); while (delayer.getDelayedTaskCount() != 0) { @@ -58,6 +59,7 @@ void testOrTimeoutWithCompleteExceptionallyDoesNotLeak() throws InterruptedExcep @Test void testCompleteOnTimeoutWithCompleteExceptionallyDoesNotLeak() throws InterruptedException { ForkJoinPool delayer = ForkJoinPool.commonPool(); + assertEquals(delayer.getDelayedTaskCount(), 0); var future = new CompletableFuture<>().completeOnTimeout(null, 12, TimeUnit.HOURS); future.completeExceptionally(new RuntimeException("This is fine")); while (delayer.getDelayedTaskCount() != 0) { From c9bc41ac4d9beb2a85a4b62a7e8bc598578ef779 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 23 Feb 2025 10:19:22 -0500 Subject: [PATCH 32/40] Standardize parameter checking --- .../java/util/concurrent/DelayScheduler.java | 30 +++-- .../java/util/concurrent/ForkJoinPool.java | 111 ++++++++---------- .../java/util/concurrent/ForkJoinTask.java | 30 ++--- 3 files changed, 86 insertions(+), 85 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index acd69cedc7417..fc6b6901c2aca 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -52,12 +52,11 @@ final class DelayScheduler extends Thread { * 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 (known, 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 - * actually being async). If processing encounters resource - * failures (possible when growing heap or ForkJoinPool WorkQueue - * arrays), tasks are cancelled. + * 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 @@ -83,9 +82,16 @@ final class DelayScheduler extends Thread { * 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 while + * 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.) 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 @@ -94,11 +100,11 @@ final class DelayScheduler extends Thread { * 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 any cancelled tasks - * seen while performing sift-down operations, in which case these + * 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 - * ScheduledForkJoinTask.cancel() (which is then a no-op if + * 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 @@ -324,7 +330,7 @@ else if (stat >= 0) { * @return current heap size */ private static int replace(ScheduledForkJoinTask[] h, int k, int n) { - if (h != null && h.length >= 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; diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 299b7f89555fa..3aa608d318b9a 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -145,10 +145,10 @@ * {@linkplain ForkJoinTask ForkJoinTasks} that implement the {@link * ScheduledFuture} interface. Resource exhaustion encountered after * initial submission results in task cancellation. When time-based - * methods are used, shutdown policies are based on the default - * policies of class {@link ScheduledThreadPoolExecutor}: upon {@link - * #shutdown}, existing periodic tasks will not re-execute, and the - * pool terminates when quiescent and existing delayed tasks + * methods are used, shutdown policies match the default policies of + * class {@link ScheduledThreadPoolExecutor}: upon {@link #shutdown}, + * existing periodic tasks will not re-execute, and the pool + * terminates when quiescent and existing delayed tasks * complete. Method {@link #cancelDelayedTasksOnShutdown} may be used * to disable all delayed tasks upon shutdown, and method {@link * #shutdownNow} may be used to instead unconditionally initiate pool @@ -925,16 +925,15 @@ public class ForkJoinPool extends AbstractExecutorService * their delays elapse (see method executeEnabledScheduledTask). * The only other interactions with the delayScheduler are to * control shutdown and maintain shutdown-related policies in - * methods quiescent() and tryTerminate(). In particular, to - * conform to policies, shutdown-related processing must deal with - * cases in which tasks are submitted before shutdown, but not - * ready until afterwards, in which case they must bypass some - * screening to be allowed to run. Conversely, the DelayScheduler - * checks runState status and when enabled, completes termination, - * using only methods shutdownStatus and tryStopIfShutdown. All of - * these methods are final and have signatures referencing - * DelaySchedulers, so cannot conflict with those of any existing - * FJP subclasses. + * methods quiescent() and tryTerminate(). In particular, + * processing must deal with cases in which tasks are submitted + * before shutdown, but not enabled until afterwards, in which + * case they must bypass some screening to be allowed to + * run. Conversely, the DelayScheduler checks runState status and + * when enabled, completes termination, using only methods + * shutdownStatus and tryStopIfShutdown. All of these methods are + * final and have signatures referencing DelaySchedulers, so + * cannot conflict with those of any existing FJP subclasses. * * Memory placement * ================ @@ -1006,7 +1005,9 @@ public class ForkJoinPool extends AbstractExecutorService * Nearly all explicit checks lead to bypass/return, not exception * throws, because they may legitimately arise during shutdown. A * few unusual loop constructions encourage (with varying - * effectiveness) JVMs about where (not) to place safepoints. + * effectiveness) JVMs about where (not) to place safepoints. All + * public methods screen arguments (mainly null checks) before + * creating or executing tasks. * * There is a lot of representation-level coupling among classes * ForkJoinPool, ForkJoinWorkerThread, and ForkJoinTask. The @@ -2617,7 +2618,7 @@ else if (rejectOnShutdown && (runState & SHUTDOWN) != 0L) { throw new RejectedExecutionException(); } - private void poolSubmit(boolean signalIfEmpty, ForkJoinTask task) { + private ForkJoinTask poolSubmit(boolean signalIfEmpty, ForkJoinTask task) { Thread t; ForkJoinWorkerThread wt; WorkQueue q; boolean internal; if (((t = JLA.currentCarrierThread()) instanceof ForkJoinWorkerThread) && (wt = (ForkJoinWorkerThread)t).pool == this) { @@ -2629,6 +2630,7 @@ private void poolSubmit(boolean signalIfEmpty, ForkJoinTask task) { q = submissionQueue(ThreadLocalRandom.getProbe(), true); } q.push(task, signalIfEmpty ? this : null, internal); + return task; } /** @@ -2960,12 +2962,9 @@ public ForkJoinPool(int parallelism, * event-style asynchronous tasks. For default value, use {@code * false}. * - * @param corePoolSize the number of threads to keep in the pool - * (unless timed out after an elapsed keep-alive). Normally (and - * by default) this is the same value as the parallelism level, - * but may be set to a larger value to reduce dynamic overhead if - * tasks regularly block. Using a smaller value (for example - * {@code 0}) has the same effect as the default. + * @param corePoolSize ignored: used in previous versions of this + * class but no longer applicable. Using {@code 0} maintains + * compatibility across versions. * * @param maximumPoolSize the maximum number of threads allowed. * When the maximum is reached, attempts to replace blocked @@ -3152,8 +3151,7 @@ static ForkJoinPool asyncCommonPool() { * scheduled for execution */ public T invoke(ForkJoinTask task) { - Objects.requireNonNull(task); - poolSubmit(true, task); + poolSubmit(true, Objects.requireNonNull(task)); try { return task.join(); } catch (RuntimeException | Error unchecked) { @@ -3172,8 +3170,7 @@ public T invoke(ForkJoinTask task) { * scheduled for execution */ public void execute(ForkJoinTask task) { - Objects.requireNonNull(task); - poolSubmit(true, task); + poolSubmit(true, Objects.requireNonNull(task)); } // AbstractExecutorService methods @@ -3186,7 +3183,7 @@ public void execute(ForkJoinTask task) { @Override @SuppressWarnings("unchecked") public void execute(Runnable task) { - poolSubmit(true, (task instanceof ForkJoinTask) + poolSubmit(true, (Objects.requireNonNull(task) instanceof ForkJoinTask) ? (ForkJoinTask) task // avoid re-wrap : new ForkJoinTask.RunnableExecuteAction(task)); } @@ -3206,9 +3203,7 @@ public void execute(Runnable task) { * scheduled for execution */ public ForkJoinTask submit(ForkJoinTask task) { - Objects.requireNonNull(task); - poolSubmit(true, task); - return task; + return poolSubmit(true, Objects.requireNonNull(task)); } /** @@ -3218,12 +3213,12 @@ public ForkJoinTask submit(ForkJoinTask task) { */ @Override public ForkJoinTask submit(Callable task) { - ForkJoinTask t = + Objects.requireNonNull(task); + return poolSubmit( + true, (Thread.currentThread() instanceof ForkJoinWorkerThread) ? new ForkJoinTask.AdaptedCallable(task) : - new ForkJoinTask.AdaptedInterruptibleCallable(task); - poolSubmit(true, t); - return t; + new ForkJoinTask.AdaptedInterruptibleCallable(task)); } /** @@ -3233,12 +3228,12 @@ public ForkJoinTask submit(Callable task) { */ @Override public ForkJoinTask submit(Runnable task, T result) { - ForkJoinTask t = + Objects.requireNonNull(task); + return poolSubmit( + true, (Thread.currentThread() instanceof ForkJoinWorkerThread) ? new ForkJoinTask.AdaptedRunnable(task, result) : - new ForkJoinTask.AdaptedInterruptibleRunnable(task, result); - poolSubmit(true, t); - return t; + new ForkJoinTask.AdaptedInterruptibleRunnable(task, result)); } /** @@ -3249,13 +3244,14 @@ public ForkJoinTask submit(Runnable task, T result) { @Override @SuppressWarnings("unchecked") public ForkJoinTask submit(Runnable task) { - ForkJoinTask f = (task instanceof ForkJoinTask) ? + Objects.requireNonNull(task); + return poolSubmit( + true, + (task instanceof ForkJoinTask) ? (ForkJoinTask) task : // avoid re-wrap ((Thread.currentThread() instanceof ForkJoinWorkerThread) ? new ForkJoinTask.AdaptedRunnable(task, null) : - new ForkJoinTask.AdaptedInterruptibleRunnable(task, null)); - poolSubmit(true, f); - return f; + new ForkJoinTask.AdaptedInterruptibleRunnable(task, null))); } /** @@ -3298,9 +3294,7 @@ public ForkJoinTask externalSubmit(ForkJoinTask task) { * @since 19 */ public ForkJoinTask lazySubmit(ForkJoinTask task) { - Objects.requireNonNull(task); - poolSubmit(false, task); - return task; + return poolSubmit(false, Objects.requireNonNull(task)); } /** @@ -3553,10 +3547,10 @@ final ScheduledForkJoinTask scheduleDelayedTask(ScheduledForkJoinTask */ public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - Objects.requireNonNull(command); return scheduleDelayedTask( - new ScheduledForkJoinTask( // implicit null check of unit - unit.toNanos(delay), 0L, false, command, null, this)); + new ScheduledForkJoinTask( + unit.toNanos(delay), 0L, false, // implicit null check of unit + Objects.requireNonNull(command), null, this)); } /** @@ -3582,10 +3576,10 @@ public ScheduledFuture schedule(Runnable command, */ public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - Objects.requireNonNull(callable); return scheduleDelayedTask( new ScheduledForkJoinTask( - unit.toNanos(delay), 0L, false, null, callable, this)); + unit.toNanos(delay), 0L, false, null, + Objects.requireNonNull(callable), this)); } /** @@ -3633,14 +3627,13 @@ public ScheduledFuture schedule(Callable callable, public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - Objects.requireNonNull(command); if (period <= 0L) throw new IllegalArgumentException(); return scheduleDelayedTask( new ScheduledForkJoinTask( unit.toNanos(initialDelay), - unit.toNanos(period), - false, command, null, this)); + unit.toNanos(period), false, + Objects.requireNonNull(command), null, this)); } /** @@ -3683,14 +3676,13 @@ public ScheduledFuture scheduleAtFixedRate(Runnable command, public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - Objects.requireNonNull(command); if (delay <= 0L) throw new IllegalArgumentException(); return scheduleDelayedTask( new ScheduledForkJoinTask( unit.toNanos(initialDelay), - -unit.toNanos(delay), // negative for fixed delay - false, command, null, this)); + -unit.toNanos(delay), false, // negative for fixed delay + Objects.requireNonNull(command), null, this)); } /** @@ -3728,7 +3720,7 @@ public void run() { * may be interrupted if running. Actions may include {@link * ForkJoinTask#complete} to set a replacement value or {@link * ForkJoinTask#completeExceptionally} to throw an appropriate - * exception. Note that these will only succeed if the task has + * exception. Note that these can succeed only if the task has * not already completed when the timeoutAction executes. * * @param callable the function to execute @@ -3756,8 +3748,7 @@ public ForkJoinTask submitWithTimeout(Callable callable, onTimeout.task = task = new ForkJoinTask.CallableWithTimeout(callable, timeoutTask); scheduleDelayedTask(timeoutTask); - poolSubmit(true, task); - return task; + return poolSubmit(true, task); } /** @@ -4395,6 +4386,7 @@ private static void unmanagedBlock(ManagedBlocker blocker) @Override protected RunnableFuture newTaskFor(Runnable runnable, T value) { + Objects.requireNonNull(runnable); return (Thread.currentThread() instanceof ForkJoinWorkerThread) ? new ForkJoinTask.AdaptedRunnable(runnable, value) : new ForkJoinTask.AdaptedInterruptibleRunnable(runnable, value); @@ -4402,6 +4394,7 @@ protected RunnableFuture newTaskFor(Runnable runnable, T value) { @Override protected RunnableFuture newTaskFor(Callable callable) { + Objects.requireNonNull(callable); return (Thread.currentThread() instanceof ForkJoinWorkerThread) ? new ForkJoinTask.AdaptedCallable(callable) : new ForkJoinTask.AdaptedInterruptibleCallable(callable); diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index e761d15de8f19..0587423db0258 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -1410,7 +1410,8 @@ public final boolean compareAndSetForkJoinTaskTag(short expect, short update) { * @return the task */ public static ForkJoinTask adapt(Runnable runnable) { - return new AdaptedRunnableAction(runnable); + return new AdaptedRunnableAction( + Objects.requireNonNull(runnable)); } /** @@ -1424,7 +1425,8 @@ public static ForkJoinTask adapt(Runnable runnable) { * @return the task */ public static ForkJoinTask adapt(Runnable runnable, T result) { - return new AdaptedRunnable(runnable, result); + return new AdaptedRunnable( + Objects.requireNonNull(runnable), result); } /** @@ -1438,7 +1440,8 @@ public static ForkJoinTask adapt(Runnable runnable, T result) { * @return the task */ public static ForkJoinTask adapt(Callable callable) { - return new AdaptedCallable(callable); + return new AdaptedCallable( + Objects.requireNonNull(callable)); } /** @@ -1456,7 +1459,8 @@ public static ForkJoinTask adapt(Callable callable) { * @since 19 */ public static ForkJoinTask adaptInterruptible(Callable callable) { - return new AdaptedInterruptibleCallable(callable); + return new AdaptedInterruptibleCallable( + Objects.requireNonNull(callable)); } /** @@ -1475,7 +1479,8 @@ public static ForkJoinTask adaptInterruptible(Callable calla * @since 22 */ public static ForkJoinTask adaptInterruptible(Runnable runnable, T result) { - return new AdaptedInterruptibleRunnable(runnable, result); + return new AdaptedInterruptibleRunnable( + Objects.requireNonNull(runnable), result); } /** @@ -1493,7 +1498,8 @@ public static ForkJoinTask adaptInterruptible(Runnable runnable, T result * @since 22 */ public static ForkJoinTask adaptInterruptible(Runnable runnable) { - return new AdaptedInterruptibleRunnable(runnable, null); + return new AdaptedInterruptibleRunnable( + Objects.requireNonNull(runnable), null); } // Serialization support @@ -1552,7 +1558,6 @@ static final class AdaptedRunnable extends ForkJoinTask @SuppressWarnings("serial") // Conditionally serializable T result; AdaptedRunnable(Runnable runnable, T result) { - Objects.requireNonNull(runnable); this.runnable = runnable; this.result = result; // OK to set this even before completion } @@ -1574,7 +1579,6 @@ static final class AdaptedRunnableAction extends ForkJoinTask @SuppressWarnings("serial") // Conditionally serializable final Runnable runnable; AdaptedRunnableAction(Runnable runnable) { - Objects.requireNonNull(runnable); this.runnable = runnable; } public final Void getRawResult() { return null; } @@ -1597,7 +1601,6 @@ static final class AdaptedCallable extends ForkJoinTask @SuppressWarnings("serial") // Conditionally serializable T result; AdaptedCallable(Callable callable) { - Objects.requireNonNull(callable); this.callable = callable; } public final T getRawResult() { return result; } @@ -1695,7 +1698,6 @@ static final class AdaptedInterruptibleCallable extends InterruptibleTask @SuppressWarnings("serial") // Conditionally serializable T result; AdaptedInterruptibleCallable(Callable callable) { - Objects.requireNonNull(callable); this.callable = callable; } public final T getRawResult() { return result; } @@ -1714,7 +1716,6 @@ static final class AdaptedInterruptibleRunnable extends InterruptibleTask @SuppressWarnings("serial") // Conditionally serializable final T result; AdaptedInterruptibleRunnable(Runnable runnable, T result) { - Objects.requireNonNull(runnable); this.runnable = runnable; this.result = result; } @@ -1732,7 +1733,6 @@ static final class RunnableExecuteAction extends InterruptibleTask { @SuppressWarnings("serial") // Conditionally serializable final Runnable runnable; RunnableExecuteAction(Runnable runnable) { - Objects.requireNonNull(runnable); this.runnable = runnable; } public final Void getRawResult() { return null; } @@ -1798,9 +1798,12 @@ final T invokeAny(Collection> tasks, throw new NullPointerException(); InvokeAnyTask t = null; // list of submitted tasks try { - for (Callable c : tasks) + for (Callable c : tasks) { + if (c == null) + throw new NullPointerException(); pool.execute((ForkJoinTask) (t = new InvokeAnyTask(c, this, t))); + } return timed ? get(nanos, TimeUnit.NANOSECONDS) : get(); } finally { for (; t != null; t = t.pred) @@ -1827,7 +1830,6 @@ static final class InvokeAnyTask extends InterruptibleTask { final InvokeAnyTask pred; // to traverse on cancellation InvokeAnyTask(Callable callable, InvokeAnyRoot root, InvokeAnyTask pred) { - Objects.requireNonNull(callable); this.callable = callable; this.root = root; this.pred = pred; From b40513fb656f0bc394da8d97dbfa424800a3746e Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Fri, 28 Feb 2025 10:07:52 -0500 Subject: [PATCH 33/40] Address review comments; reactivation tweak --- .../java/util/concurrent/DelayScheduler.java | 21 +++++++++++-------- .../java/util/concurrent/ForkJoinPool.java | 18 ++++++++-------- .../concurrent/tck/CompletableFutureTest.java | 3 ++- .../concurrent/tck/ForkJoinPool20Test.java | 6 +++--- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index fc6b6901c2aca..7ea193ec124fb 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -89,19 +89,22 @@ final class DelayScheduler extends Thread { * 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.) To reduce GC pressure and memory - * retention, these are nulled out as soon as possible. + * 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 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 + * 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 diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 3aa608d318b9a..97a9158f255e2 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -858,9 +858,9 @@ public class ForkJoinPool extends AbstractExecutorService * requirements, uses of the commonPool that require async because * caller-runs need not apply, ensure threads are enabled (by * setting parallelism) via method asyncCommonPool before - * proceeding. (In principle, these need to ensure at least one - * worker, but due to other backward compatibility contraints, - * ensure two.) + * proceeding. (In principle, overriding zero parallelism needs to + * ensure at least one worker, but due to other backward + * compatibility contraints, ensures two.) * * As a more appropriate default in managed environments, unless * overridden by system properties, we use workers of subclass @@ -2068,8 +2068,8 @@ private int deactivate(WorkQueue w, int phase) { ((e & SHUTDOWN) != 0L && ac == 0 && quiescent() > 0) || (qs = queues) == null || (n = qs.length) <= 0) return IDLE; // terminating - int prechecks = Math.min(ac, 2); // reactivation threshold - for (int k = Math.max(n + (n << 1), SPIN_WAITS << 1);;) { + int prechecks = 3; // reactivation threshold + for (int k = Math.max(n << 2, SPIN_WAITS << 1);;) { WorkQueue q; int cap; ForkJoinTask[] a; long c; if (w.phase == activePhase) return activePhase; @@ -2962,9 +2962,9 @@ public ForkJoinPool(int parallelism, * event-style asynchronous tasks. For default value, use {@code * false}. * - * @param corePoolSize ignored: used in previous versions of this + * @param corePoolSize ignored: used in previous releases of this * class but no longer applicable. Using {@code 0} maintains - * compatibility across versions. + * compatibility across releases. * * @param maximumPoolSize the maximum number of threads allowed. * When the maximum is reached, attempts to replace blocked @@ -3126,8 +3126,8 @@ public static ForkJoinPool commonPool() { */ static ForkJoinPool asyncCommonPool() { ForkJoinPool cp; int p; - if ((p = (cp = common).parallelism) < 2) - U.compareAndSetInt(cp, PARALLELISM, p, 2); + if ((p = (cp = common).parallelism) == 0) + U.compareAndSetInt(cp, PARALLELISM, 0, 2); return cp; } diff --git a/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java b/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java index 4f2f5d2d805ad..de3d1dd1050bc 100644 --- a/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java +++ b/test/jdk/java/util/concurrent/tck/CompletableFutureTest.java @@ -32,6 +32,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.CompletableFuture.completedFuture; @@ -101,7 +102,7 @@ void checkIncomplete(CompletableFuture f) { assertNull(result); try { - f.get(randomExpiredTimeout(), randomTimeUnit()); + f.get(1, NANOSECONDS); shouldThrow(); } catch (TimeoutException success) {} diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java index 5e83ef4bd8356..2a7072d4470df 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool20Test.java @@ -507,7 +507,7 @@ public void testSubmittedTasksRejectedWhenShutdown() throws InterruptedException }}; try (PoolCleaner cleaner = cleaner(p, done)) { - for (int i = p.getParallelism(); i--> 0; ) { + for (int i = p.getParallelism(); i-- > 0; ) { switch (rnd.nextInt(4)) { case 0: p.execute(r); break; case 1: assertFalse(p.submit(r).isDone()); break; @@ -520,7 +520,6 @@ public void testSubmittedTasksRejectedWhenShutdown() throws InterruptedException p.shutdown(); done.countDown(); // release blocking tasks assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - } } @@ -566,7 +565,7 @@ public void testShutdownNow_delayedTasks() throws InterruptedException { } /** - * submitWithTimeout cancels task after timeout + * submitWithTimeout (eventually) cancels task after timeout */ public void testSubmitWithTimeoutCancels() throws InterruptedException { final ForkJoinPool p = ForkJoinPool.commonPool(); @@ -629,6 +628,7 @@ public Boolean call() throws Exception { ForkJoinTask task = p.submitWithTimeout(c, LONGER_DELAY_MS, MILLISECONDS, null); Thread.sleep(timeoutMillis()); assertFalse(task.isCancelled()); + assertEquals(task.join(), Boolean.TRUE); } /** From 0c5d22a34de9dfdd12edcfaeeb565d1aa784d7f7 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sat, 1 Mar 2025 10:39:51 -0500 Subject: [PATCH 34/40] Reduce volatile reads --- .../java/util/concurrent/DelayScheduler.java | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index 7ea193ec124fb..b73ffed16cf33 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -218,6 +218,7 @@ public final void run() { active = -1; else { try { + p.onDelaySchedulerStart(this); loop(p); } finally { restingSize = 0; @@ -229,38 +230,49 @@ public final void run() { /** * After initialization, repeatedly: - * 1. Process pending tasks in batches, to add or remove from heap - * 2. Check for shutdown, either exiting or preparing for shutdown when empty - * 3. Trigger all enabled tasks by externally submitting them to pool - * 4. If active, set tentatively inactive, + * 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) { - p.onDelaySchedulerStart(this); 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 t; int runStatus; - while (pending != null && // process pending tasks + 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 { - next = t.nextPending; - long d = t.when; - int i = t.heapIndex, stat = t.status; - if (next != null) + int i; + if ((next = t.nextPending) != null) t.nextPending = null; - if (i >= 0) { - t.heapIndex = -1; + if ((i = t.heapIndex) >= 0) { + t.heapIndex = -1; // remove cancelled task if (i < cap && h[i] == t) n = replace(h, i, n); } - else if (stat >= 0) { - if (n >= cap || n < 0) // couldn't resize - t.trySetCancelled(); - else { // add and sift up + 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 && @@ -274,27 +286,28 @@ else if (stat >= 0) { h[k] = t; if (n >= cap && (newCap = cap << 1) > cap) { ScheduledForkJoinTask[] a = null; - try { // try to resize + try { // try to resize a = Arrays.copyOf(h, newCap); } catch (Error | RuntimeException ex) { } if (a != null && a.length == newCap) { - cap = newCap; // else keep using old array - h = a; + cap = newCap; + h = a; // else keep using old array } } } } } while ((t = next) != null); + q = pending; } - if ((runStatus = p.shutdownStatus(this)) != 0) { + if (p != null && (runStatus = p.shutdownStatus(this)) != 0) { if ((n = tryStop(p, h, n, runStatus, prevRunStatus)) < 0) break; prevRunStatus = runStatus; } - long parkTime = 0L; // zero for untimed park + parkTime = 0L; if (n > 0 && h.length > 0) { // submit enabled tasks long now = now(); do { @@ -309,21 +322,12 @@ else if (stat >= 0) { if (stat >= 0) { if (f.isImmediate) f.doExec(); - else + else if (p != null) p.executeEnabledScheduledTask(f); } } } while ((n = replace(h, 0, n)) > 0); } - - if (pending == null) { - restingSize = n; - Thread.interrupted(); // clear before park - if (active == 0) - U.park(false, parkTime); - else // deactivate and recheck - U.compareAndSetInt(this, ACTIVE, 1, 0); - } } } From 5c0355be6e5fbb7ba17941de109bc77f0563ba41 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sat, 8 Mar 2025 07:47:18 -0500 Subject: [PATCH 35/40] Associate probes with carriers if Virtual (no doc updates yet) --- .../java/util/concurrent/DelayScheduler.java | 167 +++++++++--------- .../java/util/concurrent/ForkJoinPool.java | 92 ++++------ .../util/concurrent/ThreadLocalRandom.java | 21 ++- 3 files changed, 141 insertions(+), 139 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java index b73ffed16cf33..358d3b69f1e68 100644 --- a/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java +++ b/src/java.base/share/classes/java/util/concurrent/DelayScheduler.java @@ -218,7 +218,6 @@ public final void run() { active = -1; else { try { - p.onDelaySchedulerStart(this); loop(p); } finally { restingSize = 0; @@ -238,95 +237,103 @@ public final void run() { * 4. Trigger all enabled tasks by submitting them to pool or run if immediate */ private void loop(ForkJoinPool p) { - 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); + 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; } - 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) { + 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; } - if (a != null && a.length == newCap) { - cap = newCap; - h = a; // else keep using old array + 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; - } + } while ((t = next) != null); + q = pending; + } - if (p != null && (runStatus = p.shutdownStatus(this)) != 0) { - if ((n = tryStop(p, h, n, runStatus, prevRunStatus)) < 0) - break; - prevRunStatus = runStatus; - } + 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) { - if (f.isImmediate) + 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 if (p != null) - p.executeEnabledScheduledTask(f); + else { + try { + p.executeEnabledScheduledTask(f); + } + catch (Error | RuntimeException ex) { + f.trySetCancelled(); + } + } } - } - } while ((n = replace(h, 0, n)) > 0); + } while ((n = replace(h, 0, n)) > 0); + } } } } diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 97a9158f255e2..63e199227b591 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -919,21 +919,21 @@ public class ForkJoinPool extends AbstractExecutorService * * This class supports ScheduledExecutorService methods by * creating and starting a DelayScheduler on first use of these - * methods (via startDelayScheduler, with callback - * onDelaySchedulerStart). The scheduler operates independently in - * its own thread, relaying tasks to the pool to execute when - * their delays elapse (see method executeEnabledScheduledTask). - * The only other interactions with the delayScheduler are to - * control shutdown and maintain shutdown-related policies in - * methods quiescent() and tryTerminate(). In particular, - * processing must deal with cases in which tasks are submitted - * before shutdown, but not enabled until afterwards, in which - * case they must bypass some screening to be allowed to - * run. Conversely, the DelayScheduler checks runState status and - * when enabled, completes termination, using only methods - * shutdownStatus and tryStopIfShutdown. All of these methods are - * final and have signatures referencing DelaySchedulers, so - * cannot conflict with those of any existing FJP subclasses. + * methods (via startDelayScheduler). The scheduler operates + * independently in its own thread, relaying tasks to the pool to + * execute when their delays elapse (see method + * executeEnabledScheduledTask). The only other interactions with + * the delayScheduler are to control shutdown and maintain + * shutdown-related policies in methods quiescent() and + * tryTerminate(). In particular, processing must deal with cases + * in which tasks are submitted before shutdown, but not enabled + * until afterwards, in which case they must bypass some screening + * to be allowed to run. Conversely, the DelayScheduler checks + * runState status and when enabled, completes termination, using + * only methods shutdownStatus and tryStopIfShutdown. All of these + * methods are final and have signatures referencing + * DelaySchedulers, so cannot conflict with those of any existing + * FJP subclasses. * * Memory placement * ================ @@ -1102,7 +1102,7 @@ public class ForkJoinPool extends AbstractExecutorService static final int DROPPED = 1 << 16; // removed from ctl counts static final int UNCOMPENSATE = 1 << 16; // tryCompensate return static final int IDLE = 1 << 16; // phase seqlock/version count - static final int MIN_QUEUES_SIZE = 4; // ensure > 1 external slot + static final int MIN_QUEUES_SIZE = 1 << 4; // ensure external slots /* * Bits and masks for ctl and bounds are packed with 4 16 bit subfields: @@ -2580,34 +2580,40 @@ final ForkJoinTask nextTaskFor(WorkQueue w) { * should be thrown when shutdown (else only if terminating) */ private WorkQueue submissionQueue(int r, boolean rejectOnShutdown) { - if (r == 0) { + int reuse; // nonzero if prefer create + if ((reuse = r) == 0) { ThreadLocalRandom.localInit(); // initialize caller's probe r = ThreadLocalRandom.getProbe(); } - for (;;) { - int n, i, id; WorkQueue[] qs; WorkQueue q, w = null; + for (int probes = 0; ; ++probes) { + int n, i, id; WorkQueue[] qs; WorkQueue q; if ((qs = queues) == null) break; if ((n = qs.length) <= 0) break; if ((q = qs[i = (id = r & EXTERNAL_ID_MASK) & (n - 1)]) == null) { - if (w == null) - w = new WorkQueue(null, id, 0, false); + WorkQueue w = new WorkQueue(null, id, 0, false); w.phase = id; boolean reject = ((lockRunState() & SHUTDOWN) != 0 && rejectOnShutdown); - if (!reject && queues == qs && qs[i] == null) { - q = qs[i] = w; // else retry - w = null; - } + if (!reject && queues == qs && qs[i] == null) + q = qs[i] = w; // else lost race to install unlockRunState(); if (q != null) return q; if (reject) break; + reuse = 0; } - else if (!q.tryLockPhase()) // move index + if (reuse == 0 || !q.tryLockPhase()) { // move index + if (reuse == 0) { + if (probes >= n >> 1) + reuse = r; // stop prefering free slot + } + else if (q != null) + reuse = 0; // probe on collision r = ThreadLocalRandom.advanceProbe(r); + } else if (rejectOnShutdown && (runState & SHUTDOWN) != 0L) { q.unlockPhase(); // check while q lock held break; @@ -3480,34 +3486,12 @@ private DelayScheduler startDelayScheduler() { return ds; } - /** - * Callback upon starting DelayScheduler - */ - final void onDelaySchedulerStart(DelayScheduler ds) { - WorkQueue q; // set up default submission queue - if ((q = submissionQueue(0, false)) != null) - q.unlockPhase(); - } - /** * Arranges execution of a ScheduledForkJoinTask whose delay has - * elapsed, or cancels it on error + * elapsed */ final void executeEnabledScheduledTask(ScheduledForkJoinTask task) { - if (task != null) { - WorkQueue q; - boolean cancel = false; - try { - if ((q = externalSubmissionQueue(false)) == null) - cancel = true; // terminating - else - q.push(task, this, false); - } catch(Error | RuntimeException ex) { - cancel = true; // OOME or VM error - } - if (cancel) - task.trySetCancelled(); - } + externalSubmissionQueue(false).push(task, this, false); } /** @@ -3691,13 +3675,13 @@ public ScheduledFuture scheduleWithFixedDelay(Runnable command, static final class TimeoutAction implements Runnable { // set after construction, nulled after use ForkJoinTask.CallableWithTimeout task; - Consumer> action; - TimeoutAction(Consumer> action) { + Consumer> action; + TimeoutAction(Consumer> action) { this.action = action; } public void run() { ForkJoinTask.CallableWithTimeout t = task; - Consumer> a = action; + Consumer> a = action; task = null; action = null; if (t != null && t.status >= 0) { @@ -3738,7 +3722,7 @@ public void run() { */ public ForkJoinTask submitWithTimeout(Callable callable, long timeout, TimeUnit unit, - Consumer> timeoutAction) { + Consumer> timeoutAction) { ForkJoinTask.CallableWithTimeout task; TimeoutAction onTimeout; Objects.requireNonNull(callable); ScheduledForkJoinTask timeoutTask = diff --git a/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java b/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java index 19eb312294773..9436b0ec6ef66 100644 --- a/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java +++ b/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java @@ -45,6 +45,7 @@ import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.LongStream; +import jdk.internal.access.JavaLangAccess; import jdk.internal.access.JavaUtilConcurrentTLRAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.random.RandomSupport; @@ -158,11 +159,19 @@ private ThreadLocalRandom() { * rely on (static) atomic generators to initialize the values. */ static final void localInit() { - int p = probeGenerator.addAndGet(PROBE_INCREMENT); - int probe = (p == 0) ? 1 : p; // skip 0 long seed = RandomSupport.mixMurmur64(seeder.getAndAdd(SEEDER_INCREMENT)); - Thread t = Thread.currentThread(); + Thread t = Thread.currentThread(), carrier; U.putLong(t, SEED, seed); + int probe = 0; // if virtual, share probe with carrier + if ((carrier = JLA.currentCarrierThread()) != t && + (probe = U.getInt(carrier, PROBE)) == 0) { + seed = RandomSupport.mixMurmur64(seeder.getAndAdd(SEEDER_INCREMENT)); + U.putLong(carrier, SEED, seed); + } + if (probe == 0 && (probe = probeGenerator.addAndGet(PROBE_INCREMENT)) == 0) + probe = 1; // skip 0 + if (carrier != t) + U.putInt(carrier, PROBE, probe); U.putInt(t, PROBE, probe); } @@ -251,7 +260,7 @@ protected int next(int bits) { * can be used to force initialization on zero return. */ static final int getProbe() { - return U.getInt(Thread.currentThread(), PROBE); + return U.getInt(JLA.currentCarrierThread(), PROBE); } /** @@ -262,7 +271,7 @@ static final int advanceProbe(int probe) { probe ^= probe << 13; // xorshift probe ^= probe >>> 17; probe ^= probe << 5; - U.putInt(Thread.currentThread(), PROBE, probe); + U.putInt(JLA.currentCarrierThread(), PROBE, probe); return probe; } @@ -378,6 +387,8 @@ private Object readResolve() { = new AtomicLong(RandomSupport.mixMurmur64(System.currentTimeMillis()) ^ RandomSupport.mixMurmur64(System.nanoTime())); + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + // used by ScopedValue private static class Access { static { From 6fe1a3bd2d3e0996c9ab357cdaa15aed4fa5d1b5 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sun, 9 Mar 2025 13:13:29 -0400 Subject: [PATCH 36/40] Disambiguate caller-runs vs Interruptible --- .../java/util/concurrent/ForkJoinPool.java | 43 ++++++++----------- .../java/util/concurrent/ForkJoinTask.java | 16 +++++-- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 63e199227b591..eeb91a3f842e1 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -387,18 +387,18 @@ public class ForkJoinPool extends AbstractExecutorService * WorkQueues are also used in a similar way for tasks submitted * to the pool. We cannot mix these tasks in the same queues used * by workers. Instead, we randomly associate submission queues - * with submitting threads, using a form of hashing. The - * ThreadLocalRandom probe value serves as a hash code for - * choosing existing queues, and may be randomly repositioned upon - * contention with other submitters. In essence, submitters act - * like workers except that they are restricted to executing local - * tasks that they submitted (or when known, subtasks thereof). - * Insertion of tasks in shared mode requires a lock. We use only - * a simple spinlock (as one role of field "phase") because - * submitters encountering a busy queue move to a different - * position to use or create other queues. They (spin) block when - * registering new queues, or indirectly elsewhere, by revisiting - * later. + * with submitting threads (or carriers when using VirtualThreads) + * using a form of hashing. The ThreadLocalRandom probe value + * serves as a hash code for choosing existing queues, and may be + * randomly repositioned upon contention with other submitters. + * In essence, submitters act like workers except that they are + * restricted to executing local tasks that they submitted (or + * when known, subtasks thereof). Insertion of tasks in shared + * mode requires a lock. We use only a simple spinlock (as one + * role of field "phase") because submitters encountering a busy + * queue move to a different position to use or create other + * queues. They (spin) block when registering new queues, or + * indirectly elsewhere, by revisiting later. * * Management * ========== @@ -885,7 +885,8 @@ public class ForkJoinPool extends AbstractExecutorService * To comply with ExecutorService specs, we use subclasses of * abstract class InterruptibleTask for tasks that require * stronger interruption and cancellation guarantees. External - * submitters never run these tasks, even if in the common pool. + * submitters never run these tasks, even if in the common pool + * (as indicated by ForkJoinTask.noUserHelp status bit). * InterruptibleTasks include a "runner" field (implemented * similarly to FutureTask) to support cancel(true). Upon pool * shutdown, runners are interrupted so they can cancel. Since @@ -1295,10 +1296,9 @@ final void push(ForkJoinTask task, ForkJoinPool pool, unlockPhase(); if (room < 0) throw new RejectedExecutionException("Queue capacity exceeded"); - if ((room == 0 || // pad for InterruptibleTasks + if ((room == 0 || // pad if no caller-run a[m & (s - ((internal || task == null || - task.getClass().getSuperclass() != - interruptibleTaskClass) ? 1 : 2))] == null) && + task.noUserHelp() == 0) ? 1 : 2))] == null) && pool != null) pool.signalWork(); // may have appeared empty } @@ -1637,11 +1637,6 @@ final boolean isApparentlyUnblocked() { */ static volatile RuntimePermission modifyThreadPermission; - /** - * Cached for faster type tests. - */ - static final Class interruptibleTaskClass; - /** * For VirtualThread intrinsics */ @@ -2023,12 +2018,11 @@ final void runWorker(WorkQueue w) { boolean propagate; int nb = q.base = b + 1; w.nsteals = ++nsteals; + int ts = t.status; w.source = j; // volatile rescan = true; if (propagate = - ((src != (src = j) || - t.getClass().getSuperclass() == - interruptibleTaskClass) && + ((src != (src = j) || t.noUserHelp() != 0) && a[nb & m] != null)) signalWork(); w.topLevelExec(t, fifo); @@ -4406,7 +4400,6 @@ protected RunnableFuture newTaskFor(Callable callable) { if ((scale & (scale - 1)) != 0) throw new Error("array index scale not a power of two"); - interruptibleTaskClass = ForkJoinTask.InterruptibleTask.class; Class dep = LockSupport.class; // ensure loaded // allow access to non-public methods JLA = SharedSecrets.getJavaLangAccess(); diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 1697c4c9680e2..b587e2eef12b7 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -273,6 +273,7 @@ final boolean casNext(Aux c, Aux v) { // used only in cancellation static final int ABNORMAL = 1 << 16; static final int THROWN = 1 << 17; static final int HAVE_EXCEPTION = DONE | ABNORMAL | THROWN; + static final int NO_USER_HELP = 1 << 24; // no external caller-run helping static final int MARKER = 1 << 30; // utility marker static final int SMASK = 0xffff; // short bits for tags static final int UNCOMPENSATE = 1 << 16; // helpJoin sentinel @@ -292,6 +293,12 @@ private int getAndBitwiseOrStatus(int v) { private boolean casStatus(int c, int v) { return U.compareAndSetInt(this, STATUS, c, v); } + final int noUserHelp() { // nonvolatile read + return U.getInt(this, STATUS) & NO_USER_HELP; + } + final void setNoUserHelp() { // for use in constructors only + U.putInt(this, STATUS, NO_USER_HELP); + } // Support for waiting and signalling @@ -476,7 +483,7 @@ else if (casAux(a, next)) */ private int awaitDone(boolean interruptible, long deadline) { ForkJoinWorkerThread wt; ForkJoinPool p; ForkJoinPool.WorkQueue q; - Thread t; boolean internal; int s; + Thread t; boolean internal; int s, ss; if (internal = (t = Thread.currentThread()) instanceof ForkJoinWorkerThread) { p = (wt = (ForkJoinWorkerThread)t).pool; @@ -487,7 +494,7 @@ private int awaitDone(boolean interruptible, long deadline) { return (((s = (p == null) ? 0 : ((this instanceof CountedCompleter) ? p.helpComplete(this, q, internal) : - (this instanceof InterruptibleTask) && !internal ? status : + !internal && ((ss = status) & NO_USER_HELP) != 0 ? ss : p.helpJoin(this, q, internal))) < 0)) ? s : awaitDone(internal ? p : null, s, interruptible, deadline); } @@ -1155,7 +1162,7 @@ public static void helpQuiesce() { */ public void reinitialize() { aux = null; - status = 0; + status &= NO_USER_HELP; } /** @@ -1634,6 +1641,9 @@ public String toString() { abstract static class InterruptibleTask extends ForkJoinTask implements RunnableFuture { transient volatile Thread runner; + InterruptibleTask() { + setNoUserHelp(); + } abstract T compute() throws Exception; public final boolean exec() { Thread.interrupted(); From 9cc670bc2cdf35c1055d6f469d4210d27d26bffc Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Tue, 11 Mar 2025 16:23:02 -0400 Subject: [PATCH 37/40] Use SharedSecrets for ThreadLocalRandomProbe; other tweaks --- .../java/util/concurrent/ForkJoinPool.java | 25 +++++++++--------- .../java/util/concurrent/ForkJoinTask.java | 7 ++--- .../util/concurrent/ThreadLocalRandom.java | 26 ++++++++++++------- .../util/concurrent/atomic/Striped64.java | 24 ++++++----------- .../access/JavaUtilConcurrentTLRAccess.java | 2 ++ 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index eeb91a3f842e1..74cacb656887e 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -1273,7 +1273,7 @@ final int queueSize() { /** * Pushes a task. Called only by owner or if already locked * - * @param task the task. Caller must ensure non-null. + * @param task the task; no-op if null * @param pool the pool to signal if was previously empty, else null * @param internal if caller owns this queue * @throws RejectedExecutionException if array could not be resized @@ -1281,7 +1281,9 @@ final int queueSize() { final void push(ForkJoinTask task, ForkJoinPool pool, boolean internal) { int s = top, b = base, m, cap, room; ForkJoinTask[] a; - if ((a = array) != null && (cap = a.length) > 0) { // else disabled + if ((a = array) != null && (cap = a.length) > 0 && // else disabled + task != null) { + int pk = task.noUserHelp() + 1; // prev slot offset if ((room = (m = cap - 1) - (s - b)) >= 0) { top = s + 1; long pos = slotOffset(m & s); @@ -1296,9 +1298,7 @@ final void push(ForkJoinTask task, ForkJoinPool pool, unlockPhase(); if (room < 0) throw new RejectedExecutionException("Queue capacity exceeded"); - if ((room == 0 || // pad if no caller-run - a[m & (s - ((internal || task == null || - task.noUserHelp() == 0) ? 1 : 2))] == null) && + if ((room == 0 || a[m & (s - pk)] == null) && pool != null) pool.signalWork(); // may have appeared empty } @@ -2016,14 +2016,13 @@ final void runWorker(WorkQueue w) { } else { boolean propagate; - int nb = q.base = b + 1; + int nb = q.base = b + 1, prevSrc = src; w.nsteals = ++nsteals; - int ts = t.status; - w.source = j; // volatile + w.source = src = j; // volatile rescan = true; + int nh = t.noUserHelp(); if (propagate = - ((src != (src = j) || t.noUserHelp() != 0) && - a[nb & m] != null)) + (prevSrc != src || nh != 0) && a[nb & m] != null) signalWork(); w.topLevelExec(t, fifo); if ((b = q.base) != nb && !propagate) @@ -2062,8 +2061,8 @@ private int deactivate(WorkQueue w, int phase) { ((e & SHUTDOWN) != 0L && ac == 0 && quiescent() > 0) || (qs = queues) == null || (n = qs.length) <= 0) return IDLE; // terminating - int prechecks = 3; // reactivation threshold - for (int k = Math.max(n << 2, SPIN_WAITS << 1);;) { + int k = Math.max(n << 2, SPIN_WAITS << 1); + for (int prechecks = k / n;;) { // reactivation threshold WorkQueue q; int cap; ForkJoinTask[] a; long c; if (w.phase == activePhase) return activePhase; @@ -2072,7 +2071,7 @@ private int deactivate(WorkQueue w, int phase) { if ((q = qs[k & (n - 1)]) == null) Thread.onSpinWait(); else if ((a = q.array) != null && (cap = a.length) > 0 && - a[q.base & (cap - 1)] != null && --prechecks < 0 && + a[q.base & (cap - 1)] != null && --prechecks <= 0 && (int)(c = ctl) == activePhase && compareAndSetCtl(c, (sp & LMASK) | ((c + RC_UNIT) & UMASK))) return w.phase = activePhase; // reactivate diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index b587e2eef12b7..c248cd74dde5f 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -273,7 +273,8 @@ final boolean casNext(Aux c, Aux v) { // used only in cancellation static final int ABNORMAL = 1 << 16; static final int THROWN = 1 << 17; static final int HAVE_EXCEPTION = DONE | ABNORMAL | THROWN; - static final int NO_USER_HELP = 1 << 24; // no external caller-run helping + static final int NUH_BIT = 24; // no external caller helping + static final int NO_USER_HELP = 1 << NUH_BIT; static final int MARKER = 1 << 30; // utility marker static final int SMASK = 0xffff; // short bits for tags static final int UNCOMPENSATE = 1 << 16; // helpJoin sentinel @@ -293,8 +294,8 @@ private int getAndBitwiseOrStatus(int v) { private boolean casStatus(int c, int v) { return U.compareAndSetInt(this, STATUS, c, v); } - final int noUserHelp() { // nonvolatile read - return U.getInt(this, STATUS) & NO_USER_HELP; + final int noUserHelp() { // nonvolatile read; return 0 or 1 + return (U.getInt(this, STATUS) & NO_USER_HELP) >>> NUH_BIT; } final void setNoUserHelp() { // for use in constructors only U.putInt(this, STATUS, NO_USER_HELP); diff --git a/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java b/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java index 9436b0ec6ef66..3713d616a3a0c 100644 --- a/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java +++ b/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java @@ -242,16 +242,18 @@ protected int next(int bits) { * the classes that use them. Briefly, a thread's "probe" value is * a non-zero hash code that (probably) does not collide with * other existing threads with respect to any power of two - * collision space. When it does collide, it is pseudo-randomly - * adjusted (using a Marsaglia XorShift). The nextSecondarySeed - * method is used in the same contexts as ThreadLocalRandom, but - * only for transient usages such as random adaptive spin/block - * sequences for which a cheap RNG suffices and for which it could - * in principle disrupt user-visible statistical properties of the - * main ThreadLocalRandom if we were to use it. + * collision space, based on carrier threads in the case of + * VirtualThreads to reduce the expected collision rate. When it + * does collide, it is pseudo-randomly adjusted (using a Marsaglia + * XorShift). The nextSecondarySeed method is used in the same + * contexts as ThreadLocalRandom, but only for transient usages + * such as random adaptive spin/block sequences for which a cheap + * RNG suffices and for which it could in principle disrupt + * user-visible statistical properties of the main + * ThreadLocalRandom if we were to use it. * - * Note: Because of package-protection issues, versions of some - * these methods also appear in some subpackage classes. + * Note: jdk SharedSecrets are used enable use in jdk classes + * outside this package. */ /** @@ -397,6 +399,12 @@ private static class Access { public int nextSecondaryThreadLocalRandomSeed() { return nextSecondarySeed(); } + public int getThreadLocalRandomProbe() { + return getProbe(); + } + public int advanceThreadLocalRandomProbe(int r) { + return advanceProbe(r); + } } ); } diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java b/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java index ad230f62ab65d..7a71ad54cb724 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java @@ -43,6 +43,8 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.function.DoubleBinaryOperator; import java.util.function.LongBinaryOperator; +import jdk.internal.access.SharedSecrets; +import jdk.internal.access.JavaUtilConcurrentTLRAccess; /** * A package-local class holding common representation and mechanics @@ -188,24 +190,18 @@ final boolean casCellsBusy() { } /** - * Returns the probe value for the current thread. - * Duplicated from ThreadLocalRandom because of packaging restrictions. + * Returns the ThreadLocalRandom probe value for the current thread. */ static final int getProbe() { - return (int) THREAD_PROBE.get(Thread.currentThread()); + return TLR.getThreadLocalRandomProbe(); } /** * Pseudo-randomly advances and records the given probe value for the * given thread. - * Duplicated from ThreadLocalRandom because of packaging restrictions. */ static final int advanceProbe(int probe) { - probe ^= probe << 13; // xorshift - probe ^= probe >>> 17; - probe ^= probe << 5; - THREAD_PROBE.set(Thread.currentThread(), probe); - return probe; + return TLR.advanceThreadLocalRandomProbe(probe); } /** @@ -371,21 +367,17 @@ else if (casBase(v = base, apply(fn, v, x))) } } + private static final JavaUtilConcurrentTLRAccess TLR = + SharedSecrets.getJavaUtilConcurrentTLRAccess(); + // VarHandle mechanics private static final VarHandle BASE; private static final VarHandle CELLSBUSY; - private static final VarHandle THREAD_PROBE; static { MethodHandles.Lookup l1 = MethodHandles.lookup(); BASE = MhUtil.findVarHandle(l1, "base", long.class); CELLSBUSY = MhUtil.findVarHandle(l1, "cellsBusy", int.class); - try { - MethodHandles.Lookup l2 = MethodHandles.privateLookupIn(Thread.class, l1); - THREAD_PROBE = MhUtil.findVarHandle(l2, "threadLocalRandomProbe", int.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } } } diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilConcurrentTLRAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilConcurrentTLRAccess.java index 5683146e72168..29434e823a4e6 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilConcurrentTLRAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilConcurrentTLRAccess.java @@ -27,4 +27,6 @@ public interface JavaUtilConcurrentTLRAccess { int nextSecondaryThreadLocalRandomSeed(); + int getThreadLocalRandomProbe(); + int advanceThreadLocalRandomProbe(int r); } From 172a235b1d13320da88ffbf3b04bc764a2339c46 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Thu, 13 Mar 2025 14:00:45 -0400 Subject: [PATCH 38/40] Reword javadoc --- .../share/classes/java/util/concurrent/atomic/Striped64.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java b/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java index 7a71ad54cb724..e743947a6ff52 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java @@ -190,7 +190,7 @@ final boolean casCellsBusy() { } /** - * Returns the ThreadLocalRandom probe value for the current thread. + * Returns the ThreadLocalRandom probe value for the current carrier thread. */ static final int getProbe() { return TLR.getThreadLocalRandomProbe(); @@ -198,7 +198,7 @@ static final int getProbe() { /** * Pseudo-randomly advances and records the given probe value for the - * given thread. + * given carrier thread. */ static final int advanceProbe(int probe) { return TLR.advanceThreadLocalRandomProbe(probe); From 9b51b7a37711371b40466f6f322608bafe9d65e8 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Fri, 14 Mar 2025 05:30:43 -0400 Subject: [PATCH 39/40] Use TC_MASK in accord with https://bugs.openjdk.org/browse/JDK-8330017 (Unnecessarily for now.) --- .../share/classes/java/util/concurrent/ForkJoinPool.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 74cacb656887e..8d589d4b86842 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -2134,7 +2134,8 @@ private int tryTrim(WorkQueue w, int phase, long deadline) { else if (deadline - System.currentTimeMillis() >= TIMEOUT_SLOP) stat = 0; // spurious wakeup else if (!compareAndSetCtl( - c, nc = (w.stackPred & LMASK) | (UMASK & (c - TC_UNIT)))) + c, nc = ((w.stackPred & LMASK) | (RC_MASK & c) | + (TC_MASK & (c - TC_UNIT))))) stat = -1; // lost race to signaller else { stat = 1; From 24422e4fec4e72582de3a7dff3a38110f8753190 Mon Sep 17 00:00:00 2001 From: Doug Lea Date: Sat, 22 Mar 2025 07:05:02 -0400 Subject: [PATCH 40/40] Match indent of naster changes --- .../share/classes/java/util/concurrent/ForkJoinPool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 8d589d4b86842..88b240fbacd57 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -2135,7 +2135,7 @@ else if (deadline - System.currentTimeMillis() >= TIMEOUT_SLOP) stat = 0; // spurious wakeup else if (!compareAndSetCtl( c, nc = ((w.stackPred & LMASK) | (RC_MASK & c) | - (TC_MASK & (c - TC_UNIT))))) + (TC_MASK & (c - TC_UNIT))))) stat = -1; // lost race to signaller else { stat = 1;