diff options
Diffstat (limited to 'test/719-varhandle-concurrency/src/Main.java')
-rw-r--r-- | test/719-varhandle-concurrency/src/Main.java | 381 |
1 files changed, 190 insertions, 191 deletions
diff --git a/test/719-varhandle-concurrency/src/Main.java b/test/719-varhandle-concurrency/src/Main.java index 577cdbf07e..47f523f446 100644 --- a/test/719-varhandle-concurrency/src/Main.java +++ b/test/719-varhandle-concurrency/src/Main.java @@ -33,197 +33,196 @@ import java.util.function.Consumer; * were not run at all (skipped by all threads). */ public class Main { - private static final VarHandle QA; - static { - QA = MethodHandles.arrayElementVarHandle(TestTask[].class); - } - - private static final int TASK_COUNT = 10000; - private static final int THREAD_COUNT = 20; - /* Each test may need several retries before a concurrent failure is seen. In the past, for a - * known bug, between 5 and 10 retries were sufficient. Use RETRIES to configure how many - * iterations to retry for each test scenario. However, to avoid the test running for too long, - * for example with gcstress, set a cap duration in MAX_RETRIES_DURATION. With this at least one - * iteration would run, but there could be fewer retries if each of them takes too long. */ - private static final int RETRIES = 50; - // b/235431387: timeout reduced from 1 minute - private static final Duration MAX_RETRIES_DURATION = Duration.ofSeconds(15); - - public static void main(String[] args) throws Throwable { - testConcurrentProcessing(new CompareAndExchangeRunnerFactory(), "compareAndExchange"); - testConcurrentProcessing(new CompareAndSetRunnerFactory(), "compareAndSet"); - testConcurrentProcessing(new WeakCompareAndSetRunnerFactory(), "weakCompareAndSet"); - } - - private static void testConcurrentProcessing(RunnerFactory factory, - String testName) throws Throwable { - final Duration startTs = Duration.ofNanos(System.nanoTime()); - final Duration endTs = startTs.plus(MAX_RETRIES_DURATION); - for (int i = 0; i < RETRIES; ++i) { - concurrentProcessingTestIteration(factory, i, testName); - Duration now = Duration.ofNanos(System.nanoTime()); - if (0 < now.compareTo(endTs)) { - break; - } + private static final VarHandle QA; + static { + QA = MethodHandles.arrayElementVarHandle(TestTask[].class); } - } - - private static void concurrentProcessingTestIteration(RunnerFactory factory, - int iteration, String testName) throws Throwable { - final TestTask[] tasks = new TestTask[TASK_COUNT]; - final AtomicInteger result = new AtomicInteger(); - - for (int i = 0; i < TASK_COUNT; ++i) { - tasks[i] = new TestTask(Integer.valueOf(i+1), result::addAndGet); - } - - Thread[] threads = new Thread[THREAD_COUNT]; - for (int i = 0; i < THREAD_COUNT; ++i) { - threads[i] = factory.createRunner(tasks); - } - - for (int i = 0; i < THREAD_COUNT; ++i) { - threads[i].start(); - } - - for (int i = 0; i < THREAD_COUNT; ++i) { - threads[i].join(); - } - - check(result.get(), TASK_COUNT * (TASK_COUNT + 1) / 2, - testName + " test result not as expected", iteration); - } - - /** - * Processes the task queue until there are no tasks left. - * - * The actual task-grabbing mechanism is implemented in subclasses through grabTask(). This allows - * testing various mechanisms, like compareAndSet() and compareAndExchange(). - */ - private static abstract class TaskRunner extends Thread { - - protected final TestTask[] tasks; - - TaskRunner(TestTask[] tasks) { - this.tasks = tasks; - } - - @Override - public void run() { - int i = 0; - while (i < TASK_COUNT) { - TestTask t = (TestTask) QA.get(tasks, i); - if (t == null) { - ++i; - continue; - } - if (!grabTask(t, i)) { - continue; - } - ++i; - VarHandle.releaseFence(); - t.exec(); - } - } - - /** - * Grabs the next task from the queue in an atomic way. - * - * Once a task is retrieved successfully, the queue should no longer hold a reference to it. - * This would be done, for example, by swapping the task with a null value. - * - * @param t The task to get from the queue - * @param i The index where the task is found - * - * @return {@code true} if the task has been retrieved and is not available to any other - * threads. Otherwise {@code false}. If {@code false} is returned, then either the task was no - * longer present on the queue due to another thread grabbing it, or, in case of spurious - * failure, the task is still available and no other thread managed to grab it. - */ - protected abstract boolean grabTask(TestTask t, int i); - } - - private static class TaskRunnerWithCompareAndExchange extends TaskRunner { - - TaskRunnerWithCompareAndExchange(TestTask[] tasks) { - super(tasks); - } - - @Override - protected boolean grabTask(TestTask t, int i) { - return (t == QA.compareAndExchange(tasks, i, t, null)); - } - } - - private static class TaskRunnerWithCompareAndSet extends TaskRunner { - - TaskRunnerWithCompareAndSet(TestTask[] tasks) { - super(tasks); - } - - @Override - protected boolean grabTask(TestTask t, int i) { - return QA.compareAndSet(tasks, i, t, null); - } - } - - private static class TaskRunnerWithWeakCompareAndSet extends TaskRunner { - - TaskRunnerWithWeakCompareAndSet(TestTask[] tasks) { - super(tasks); - } - - @Override - protected boolean grabTask(TestTask t, int i) { - return QA.weakCompareAndSet(tasks, i, t, null); - } - } - - - private interface RunnerFactory { - Thread createRunner(TestTask[] tasks); - } - - private static class CompareAndExchangeRunnerFactory implements RunnerFactory { - @Override - public Thread createRunner(TestTask[] tasks) { - return new TaskRunnerWithCompareAndExchange(tasks); - } - } - - private static class CompareAndSetRunnerFactory implements RunnerFactory { - @Override - public Thread createRunner(TestTask[] tasks) { - return new TaskRunnerWithCompareAndSet(tasks); - } - } - - private static class WeakCompareAndSetRunnerFactory implements RunnerFactory { - @Override - public Thread createRunner(TestTask[] tasks) { - return new TaskRunnerWithWeakCompareAndSet(tasks); - } - } - - private static class TestTask { - private final Integer ord; - private final Consumer<Integer> action; - - TestTask(Integer ord, Consumer<Integer> action) { - this.ord = ord; - this.action = action; - } - - public void exec() { - action.accept(ord); - } - } - - private static void check(int actual, int expected, String msg, int iteration) { - if (actual != expected) { - System.err.println(String.format("[iteration %d] %s : %d != %d", - iteration, msg, actual, expected)); - System.exit(1); + + private static final int TASK_COUNT = 10000; + private static final int THREAD_COUNT = 20; + /* Each test may need several retries before a concurrent failure is seen. In the past, for a + * known bug, between 5 and 10 retries were sufficient. Use RETRIES to configure how many + * iterations to retry for each test scenario. However, to avoid the test running for too long, + * for example with gcstress, set a cap duration in MAX_RETRIES_DURATION. With this at least one + * iteration would run, but there could be fewer retries if each of them takes too long. */ + private static final int RETRIES = 50; + // b/235431387: timeout reduced from 1 minute + private static final Duration MAX_RETRIES_DURATION = Duration.ofSeconds(15); + + public static void main(String[] args) throws Throwable { + testConcurrentProcessing(new CompareAndExchangeRunnerFactory(), "compareAndExchange"); + testConcurrentProcessing(new CompareAndSetRunnerFactory(), "compareAndSet"); + testConcurrentProcessing(new WeakCompareAndSetRunnerFactory(), "weakCompareAndSet"); + } + + private static void testConcurrentProcessing(RunnerFactory factory, String testName) + throws Throwable { + final Duration startTs = Duration.ofNanos(System.nanoTime()); + final Duration endTs = startTs.plus(MAX_RETRIES_DURATION); + for (int i = 0; i < RETRIES; ++i) { + concurrentProcessingTestIteration(factory, i, testName); + Duration now = Duration.ofNanos(System.nanoTime()); + if (0 < now.compareTo(endTs)) { + break; + } + } + } + + private static void concurrentProcessingTestIteration( + RunnerFactory factory, int iteration, String testName) throws Throwable { + final TestTask[] tasks = new TestTask[TASK_COUNT]; + final AtomicInteger result = new AtomicInteger(); + + for (int i = 0; i < TASK_COUNT; ++i) { + tasks[i] = new TestTask(Integer.valueOf(i + 1), result::addAndGet); + } + + Thread[] threads = new Thread[THREAD_COUNT]; + for (int i = 0; i < THREAD_COUNT; ++i) { + threads[i] = factory.createRunner(tasks); + } + + for (int i = 0; i < THREAD_COUNT; ++i) { + threads[i].start(); + } + + for (int i = 0; i < THREAD_COUNT; ++i) { + threads[i].join(); + } + + check(result.get(), + TASK_COUNT * (TASK_COUNT + 1) / 2, + testName + " test result not as expected", + iteration); + } + + /** + * Processes the task queue until there are no tasks left. + * + * The actual task-grabbing mechanism is implemented in subclasses through grabTask(). + * This allows testing various mechanisms, like compareAndSet() and compareAndExchange(). + */ + private static abstract class TaskRunner extends Thread { + + protected final TestTask[] tasks; + + TaskRunner(TestTask[] tasks) { + this.tasks = tasks; + } + + @Override + public void run() { + int i = 0; + while (i < TASK_COUNT) { + TestTask t = (TestTask) QA.get(tasks, i); + if (t == null) { + ++i; + continue; + } + if (!grabTask(t, i)) { + continue; + } + ++i; + VarHandle.releaseFence(); + t.exec(); + } + } + + /** + * Grabs the next task from the queue in an atomic way. + * + * Once a task is retrieved successfully, the queue should no longer hold a reference to it. + * This would be done, for example, by swapping the task with a null value. + * + * @param t The task to get from the queue + * @param i The index where the task is found + * + * @return {@code true} if the task has been retrieved and is not available to any other + * threads. Otherwise {@code false}. If {@code false} is returned, then either the task was + * no longer present on the queue due to another thread grabbing it, or, in case of spurious + * failure, the task is still available and no other thread managed to grab it. + */ + protected abstract boolean grabTask(TestTask t, int i); + } + + private static class TaskRunnerWithCompareAndExchange extends TaskRunner { + TaskRunnerWithCompareAndExchange(TestTask[] tasks) { + super(tasks); + } + + @Override + protected boolean grabTask(TestTask t, int i) { + return (t == QA.compareAndExchange(tasks, i, t, null)); + } + } + + private static class TaskRunnerWithCompareAndSet extends TaskRunner { + TaskRunnerWithCompareAndSet(TestTask[] tasks) { + super(tasks); + } + + @Override + protected boolean grabTask(TestTask t, int i) { + return QA.compareAndSet(tasks, i, t, null); + } + } + + private static class TaskRunnerWithWeakCompareAndSet extends TaskRunner { + TaskRunnerWithWeakCompareAndSet(TestTask[] tasks) { + super(tasks); + } + + @Override + protected boolean grabTask(TestTask t, int i) { + return QA.weakCompareAndSet(tasks, i, t, null); + } + } + + + private interface RunnerFactory { + Thread createRunner(TestTask[] tasks); + } + + private static class CompareAndExchangeRunnerFactory implements RunnerFactory { + @Override + public Thread createRunner(TestTask[] tasks) { + return new TaskRunnerWithCompareAndExchange(tasks); + } + } + + private static class CompareAndSetRunnerFactory implements RunnerFactory { + @Override + public Thread createRunner(TestTask[] tasks) { + return new TaskRunnerWithCompareAndSet(tasks); + } + } + + private static class WeakCompareAndSetRunnerFactory implements RunnerFactory { + @Override + public Thread createRunner(TestTask[] tasks) { + return new TaskRunnerWithWeakCompareAndSet(tasks); + } + } + + private static class TestTask { + private final Integer ord; + private final Consumer<Integer> action; + + TestTask(Integer ord, Consumer<Integer> action) { + this.ord = ord; + this.action = action; + } + + public void exec() { + action.accept(ord); + } + } + + private static void check(int actual, int expected, String msg, int iteration) { + if (actual != expected) { + System.err.println(String.format( + "[iteration %d] %s : %d != %d", iteration, msg, actual, expected)); + System.exit(1); + } } - } } |