1 /*
   2  * Copyright (c) 2019, 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  * @summary enumerate(list,n,recurse) may return 0 if the group is being destroyed,
  27  *          whereas it should never return a value < n. This lead to inconsistent
  28  *          results if ThreadGroup::enumerate is called concurrently at the same
  29  *          time that a child group is being destroyed. This is a race condition,
  30  *          and this test will not always fail without the fix, but it does fail
  31  *          often enough.
  32  * @bug 8219197
  33  *          
  34  */
  35 import java.util.concurrent.CountDownLatch;
  36 import java.util.concurrent.Semaphore;
  37 import java.util.concurrent.atomic.AtomicInteger;
  38 
  39 public class Destroy {
  40 
  41     static final class Task implements Runnable {
  42         final Semaphore sem;
  43         final CountDownLatch count;
  44 
  45         public Task(Semaphore sem, CountDownLatch count) {
  46             this.sem = sem;
  47             this.count = count;
  48         }
  49 
  50         @Override
  51         public void run() {
  52             try {
  53                 count.countDown();
  54                 sem.acquire();
  55             } catch (Throwable t) {
  56                 t.printStackTrace();
  57             } finally {
  58                 System.out.println(Thread.currentThread().getName()
  59                         + " exiting");
  60             }
  61         }
  62     }
  63 
  64     public static void main(String[] args) throws Exception {
  65         testDestroyChild();
  66     }
  67 
  68     public static void testDestroyChild() throws Exception {
  69         ThreadGroup root = new ThreadGroup("root");
  70         ThreadGroup parent = new ThreadGroup(root,"parent");
  71         ThreadGroup child1 = new ThreadGroup(parent, "child1");
  72         CountDownLatch count = new CountDownLatch(2);
  73         Semaphore sem1 = new Semaphore(1);
  74         Semaphore sem2 = new Semaphore(1);
  75         Thread t1 = new Thread(parent, new Task(sem1, count), "PT1");
  76         Thread t2 = new Thread(parent, new Task(sem2, count), "PT2");
  77         sem1.acquire();
  78         sem2.acquire();
  79         try {
  80 
  81             t1.start();
  82             t2.start();
  83 
  84             System.out.println("\nAwaiting parent threads...");
  85             count.await();
  86             Thread[] threads = new Thread[2];
  87             int nb = root.enumerate(threads, true);
  88             if (nb != 2) {
  89                 throw new AssertionError("wrong number of threads: " + nb);
  90             }
  91 
  92             Thread t3 = new Thread(child1::destroy, "destroy");
  93             AtomicInteger nbr = new AtomicInteger();
  94             Thread t4 = new Thread("enumerate") {
  95                 public void run() {
  96                     Thread[] threads = new Thread[42];
  97                     nbr.addAndGet(root.enumerate(threads, true));
  98                 }
  99             };
 100             t4.start(); t3.start();
 101             t4.join(); t3.join();
 102             if (nbr.get() != nb) {
 103                 throw new AssertionError("wrong number of threads: " + nbr.get());
 104             }
 105 
 106         } finally {
 107             sem1.release();
 108             sem2.release();
 109         }
 110         t1.join();
 111         t2.join();
 112     }
 113 }