1 /*
   2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @run testng AsyncShutdownNowInvokeAnyRace
  27  * @summary A variant of AsyncShutdownNow useful for race bug hunting
  28  */
  29 
  30 // TODO: reorganize all of the AsyncShutdown tests
  31 
  32 import java.util.Collections;
  33 import java.util.List;
  34 import java.util.concurrent.Callable;
  35 import java.util.concurrent.CancellationException;
  36 import java.util.concurrent.CompletableFuture;
  37 import java.util.concurrent.CountDownLatch;
  38 import java.util.concurrent.ExecutionException;
  39 import java.util.concurrent.ForkJoinPool;
  40 import java.util.concurrent.Future;
  41 import java.util.concurrent.RejectedExecutionException;
  42 import java.util.concurrent.ThreadLocalRandom;
  43 import java.util.concurrent.TimeoutException;
  44 import java.util.concurrent.TimeUnit;
  45 import java.util.concurrent.atomic.AtomicInteger;
  46 import java.util.concurrent.atomic.AtomicReference;
  47 import static java.util.concurrent.TimeUnit.SECONDS;
  48 
  49 import java.lang.management.ManagementFactory;
  50 import java.lang.management.LockInfo;
  51 import java.lang.management.ThreadInfo;
  52 import java.lang.management.ThreadMXBean;
  53 
  54 import org.testng.annotations.Test;
  55 import static org.testng.Assert.*;
  56 
  57 public class AsyncShutdownNowInvokeAnyRace {
  58 
  59     // TODO: even more jitter-inducing parallelism?
  60 
  61 //         int nThreads = ThreadLocalRandom.current().nextInt(1, 50);
  62 //         ExecutorService pool = Executors.newCachedThreadPool();
  63 //         Callable<Void> task = () -> { testInvokeAny_1(); return null; };
  64 //         List<Callable<Void>> tasks = Collections.nCopies(nThreads, task);
  65 //         try {
  66 //             for (Future<Void> future : pool.invokeAll(tasks)) {
  67 //                 future.get();
  68 //             }
  69 //         } finally {
  70 //             pool.shutdown();
  71 //         }
  72 //     }
  73 
  74 //     public void testInvokeAny_1() throws Exception {
  75 
  76     /**
  77      * Test shutdownNow with thread blocked in invokeAny.
  78      */
  79     @Test
  80     public void testInvokeAny() throws Exception {
  81         final int reps = 30_000;
  82         ThreadLocalRandom rnd = ThreadLocalRandom.current();
  83         int falseAlarms = 0;
  84         for (int rep = 1; rep < reps; rep++) {
  85             ForkJoinPool pool = new ForkJoinPool(1);
  86             CountDownLatch pleaseShutdownNow = new CountDownLatch(1);
  87             int nTasks = rnd.nextInt(2, 5);
  88             AtomicInteger threadsStarted = new AtomicInteger(0);
  89             AtomicReference<String> poolAtShutdownNow = new AtomicReference<>();
  90             Callable<Void> blockPool = () -> {
  91                 threadsStarted.getAndIncrement();
  92                 // await submission quiescence; may false-alarm
  93                 // TODO: consider re-checking to reduce false alarms
  94                 while (threadsStarted.get() + pool.getQueuedSubmissionCount() < nTasks)
  95                     Thread.yield();
  96                 pleaseShutdownNow.countDown();
  97                 Thread.sleep(86400_000);
  98                 return null;
  99             };
 100             List<Callable<Void>> tasks = Collections.nCopies(nTasks, blockPool);
 101             Runnable shutdown = () -> {
 102                 try {
 103                     pleaseShutdownNow.await();
 104                     poolAtShutdownNow.set(pool.toString());
 105                     pool.shutdownNow();
 106                 } catch (Throwable t) { throw new AssertionError(t); }
 107             };
 108             Future<Void> shutdownResult = CompletableFuture.runAsync(shutdown);
 109             try {
 110                 try {
 111                     if (rnd.nextBoolean())
 112                         pool.invokeAny(tasks, 10L, SECONDS);
 113                     else
 114                         pool.invokeAny(tasks);
 115                     throw new AssertionError("should throw");
 116                 } catch (RejectedExecutionException re) {
 117                     falseAlarms++;
 118                 } catch (CancellationException re) {
 119                 } catch (ExecutionException ex) {
 120                     Throwable cause = ex.getCause();
 121                     if (!(cause instanceof InterruptedException) &&
 122                         !(cause instanceof CancellationException))
 123                         throw ex;
 124                 } catch (TimeoutException ex) {
 125                     dumpTestThreads();
 126                     int i = rep;
 127                     String detail = String.format(
 128                         "invokeAny timed out, "
 129                         + "nTasks=%d rep=%d threadsStarted=%d%n"
 130                         + "poolAtShutdownNow=%s%n"
 131                         + "poolAtTimeout=%s%n"
 132                         + "queuedTaskCount=%d queuedSubmissionCount=%d",
 133                         nTasks, i, threadsStarted.get(),
 134                         poolAtShutdownNow,
 135                         pool,
 136                         pool.getQueuedTaskCount(),
 137                         pool.getQueuedSubmissionCount());
 138                     throw new AssertionError(detail, ex);
 139                 }
 140             } finally {
 141                 pool.shutdown();
 142             }
 143             if (falseAlarms != 0)
 144                 System.out.println("Premature shutdowns = " + falseAlarms);
 145             shutdownResult.get();
 146         }
 147     }
 148 
 149     //--- thread stack dumping (from JSR166TestCase.java) ---
 150 
 151     private static final ThreadMXBean THREAD_MXBEAN
 152         = ManagementFactory.getThreadMXBean();
 153 
 154     /** Returns true if thread info might be useful in a thread dump. */
 155     static boolean threadOfInterest(ThreadInfo info) {
 156         final String name = info.getThreadName();
 157         String lockName;
 158         if (name == null)
 159             return true;
 160         if (name.equals("Signal Dispatcher")
 161             || name.equals("WedgedTestDetector"))
 162             return false;
 163         if (name.equals("Reference Handler")) {
 164             // Reference Handler stacktrace changed in JDK-8156500
 165             StackTraceElement[] stackTrace; String methodName;
 166             if ((stackTrace = info.getStackTrace()) != null
 167                 && stackTrace.length > 0
 168                 && (methodName = stackTrace[0].getMethodName()) != null
 169                 && methodName.equals("waitForReferencePendingList"))
 170                 return false;
 171             // jdk8 Reference Handler stacktrace
 172             if ((lockName = info.getLockName()) != null
 173                 && lockName.startsWith("java.lang.ref"))
 174                 return false;
 175         }
 176         if ((name.equals("Finalizer") || name.equals("Common-Cleaner"))
 177             && (lockName = info.getLockName()) != null
 178             && lockName.startsWith("java.lang.ref"))
 179             return false;
 180         if (name.startsWith("ForkJoinPool.commonPool-worker")
 181             && (lockName = info.getLockName()) != null
 182             && lockName.startsWith("java.util.concurrent.ForkJoinPool"))
 183             return false;
 184         return true;
 185     }
 186 
 187     /**
 188      * A debugging tool to print stack traces of most threads, as jstack does.
 189      * Uninteresting threads are filtered out.
 190      */
 191     static void dumpTestThreads() {
 192         System.err.println("------ stacktrace dump start ------");
 193         for (ThreadInfo info : THREAD_MXBEAN.dumpAllThreads(true, true))
 194             if (threadOfInterest(info))
 195                 System.err.print(info);
 196         System.err.println("------ stacktrace dump end ------");
 197     }
 198 
 199 }