1 /*
   2  * Copyright (c) 2007, 2016, 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  * @bug 6460501 6236036 6500694 6490770
  27  * @summary Repeated failed timed waits shouldn't leak memory
  28  * @library /test/lib
  29  * @author Martin Buchholz
  30  */
  31 
  32 import static java.util.concurrent.TimeUnit.MILLISECONDS;
  33 import static java.util.concurrent.TimeUnit.NANOSECONDS;
  34 
  35 import java.io.File;
  36 import java.io.FileOutputStream;
  37 import java.io.IOException;
  38 import java.io.InputStream;
  39 import java.io.InputStreamReader;
  40 import java.io.OutputStream;
  41 import java.io.PrintStream;
  42 import java.io.Reader;
  43 import java.lang.ref.ReferenceQueue;
  44 import java.lang.ref.WeakReference;
  45 import java.util.concurrent.BlockingQueue;
  46 import java.util.concurrent.Callable;
  47 import java.util.concurrent.CountDownLatch;
  48 import java.util.concurrent.CyclicBarrier;
  49 import java.util.concurrent.ExecutorService;
  50 import java.util.concurrent.Executors;
  51 import java.util.concurrent.Future;
  52 import java.util.concurrent.LinkedBlockingQueue;
  53 import java.util.concurrent.Semaphore;
  54 import java.util.concurrent.ThreadLocalRandom;
  55 import java.util.concurrent.locks.ReentrantLock;
  56 import java.util.concurrent.locks.ReentrantReadWriteLock;
  57 import java.util.regex.Matcher;
  58 import java.util.regex.Pattern;
  59 import jdk.test.lib.Utils;
  60 
  61 public class TimedAcquireLeak {
  62     static final long LONG_DELAY_MS = Utils.adjustTimeout(10_000);
  63 
  64     static String javahome() {
  65         String jh = System.getProperty("java.home");
  66         return (jh.endsWith("jre")) ? jh.substring(0, jh.length() - 4) : jh;
  67     }
  68 
  69     static final File bin = new File(javahome(), "bin");
  70 
  71     static String javaProgramPath(String programName) {
  72         return new File(bin, programName).getPath();
  73     }
  74 
  75     static final String java = javaProgramPath("java");
  76     static final String jmap = javaProgramPath("jmap");
  77     static final String jps  = javaProgramPath("jps");
  78 
  79     static String outputOf(Reader r) throws IOException {
  80         final StringBuilder sb = new StringBuilder();
  81         final char[] buf = new char[1024];
  82         int n;
  83         while ((n = r.read(buf)) > 0)
  84             sb.append(buf, 0, n);
  85         return sb.toString();
  86     }
  87 
  88     static String outputOf(InputStream is) throws IOException {
  89         return outputOf(new InputStreamReader(is, "UTF-8"));
  90     }
  91 
  92     static final ExecutorService drainers = Executors.newFixedThreadPool(12);
  93     static Future<String> futureOutputOf(final InputStream is) {
  94         return drainers.submit(
  95             new Callable<String>() { public String call() throws IOException {
  96                     return outputOf(is); }});}
  97 
  98     static String outputOf(final Process p) {
  99         try {
 100             Future<String> outputFuture = futureOutputOf(p.getInputStream());
 101             Future<String> errorFuture = futureOutputOf(p.getErrorStream());
 102             final String output = outputFuture.get();
 103             final String error = errorFuture.get();
 104             // Check for successful process completion
 105             equal(error, "");
 106             equal(p.waitFor(), 0);
 107             equal(p.exitValue(), 0);
 108             return output;
 109         } catch (Throwable t) { unexpected(t); throw new Error(t); }
 110     }
 111 
 112     static String commandOutputOf(String... cmd) {
 113         try { return outputOf(new ProcessBuilder(cmd).start()); }
 114         catch (Throwable t) { unexpected(t); throw new Error(t); }
 115     }
 116 
 117     // To be called exactly twice by the parent process
 118     static <T> T rendezvousParent(Process p,
 119                                   Callable<T> callable) throws Throwable {
 120         p.getInputStream().read();
 121         T result = callable.call();
 122         sendByte(p.getOutputStream());
 123         return result;
 124     }
 125 
 126     /** No guarantees, but effective in practice. */
 127     private static void forceFullGc() {
 128         long timeoutMillis = 1000L;
 129         CountDownLatch finalized = new CountDownLatch(1);
 130         ReferenceQueue<Object> queue = new ReferenceQueue<>();
 131         WeakReference<Object> ref = new WeakReference<>(
 132             new Object() { protected void finalize() { finalized.countDown(); }},
 133             queue);
 134         try {
 135             for (int tries = 3; tries--> 0; ) {
 136                 System.gc();
 137                 if (finalized.await(timeoutMillis, MILLISECONDS)
 138                     && queue.remove(timeoutMillis) != null
 139                     && ref.get() == null) {
 140                     System.runFinalization(); // try to pick up stragglers
 141                     return;
 142                 }
 143                 timeoutMillis *= 4;
 144             }
 145         } catch (InterruptedException unexpected) {
 146             throw new AssertionError("unexpected InterruptedException");
 147         }
 148         throw new AssertionError("failed to do a \"full\" gc");
 149     }
 150 
 151     // To be called exactly twice by the child process
 152     public static void rendezvousChild() {
 153         try {
 154             forceFullGc();
 155             sendByte(System.out);
 156             System.in.read();
 157         } catch (Throwable t) { throw new Error(t); }
 158     }
 159 
 160     static String match(String s, String regex, int group) {
 161         Matcher matcher = Pattern.compile(regex).matcher(s);
 162         matcher.find();
 163         return matcher.group(group);
 164     }
 165 
 166     /** It's all about sending a message! */
 167     static void sendByte(OutputStream s) throws IOException {
 168         s.write('!');
 169         s.flush();
 170     }
 171 
 172     static int objectsInUse(final Process child,
 173                             final String childPid,
 174                             final String className) {
 175         final String regex =
 176             "(?m)^ *[0-9]+: +([0-9]+) +[0-9]+ +\\Q"+className+"\\E(?:$| )";
 177         final Callable<Integer> objectsInUse =
 178             new Callable<Integer>() { public Integer call() {
 179                 Integer i = Integer.parseInt(
 180                     match(commandOutputOf(jmap, "-histo:live", childPid),
 181                           regex, 1));
 182                 if (i > 100)
 183                     System.out.print(
 184                         commandOutputOf(jmap,
 185                                         "-dump:file=dump,format=b",
 186                                         childPid));
 187                 return i;
 188             }};
 189         try { return rendezvousParent(child, objectsInUse); }
 190         catch (Throwable t) { unexpected(t); return -1; }
 191     }
 192 
 193     static void realMain(String[] args) throws Throwable {
 194         // jmap doesn't work on Windows
 195         if (System.getProperty("os.name").startsWith("Windows"))
 196             return;
 197 
 198         final String childClassName = Job.class.getName();
 199         final String classToCheckForLeaks = Job.classToCheckForLeaks();
 200         final String uniqueID =
 201             String.valueOf(ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE));
 202 
 203         final String[] jobCmd = {
 204             java, "-Xmx8m", "-XX:+UsePerfData",
 205             "-classpath", System.getProperty("test.class.path"),
 206             childClassName, uniqueID
 207         };
 208         final Process p = new ProcessBuilder(jobCmd).start();
 209         // Ensure subprocess jvm has started, so that jps can find it
 210         p.getInputStream().read();
 211         sendByte(p.getOutputStream());
 212 
 213         final String childPid =
 214             match(commandOutputOf(jps, "-m"),
 215                   "(?m)^ *([0-9]+) +\\Q"+childClassName+"\\E *"+uniqueID+"$", 1);
 216 
 217         final int n0 = objectsInUse(p, childPid, classToCheckForLeaks);
 218         final int n1 = objectsInUse(p, childPid, classToCheckForLeaks);
 219         equal(p.waitFor(), 0);
 220         equal(p.exitValue(), 0);
 221         failed += p.exitValue();
 222 
 223         // Check that no objects were leaked.
 224         //
 225         // TODO: This test is very brittle, depending on current JDK
 226         // implementation, and needing occasional adjustment.
 227         System.out.printf("%d -> %d%n", n0, n1);
 228         // Almost always n0 == n1
 229         // Maximum jitter observed in practice is 10 -> 17
 230         check(Math.abs(n1 - n0) < 10);
 231         check(n1 < 25);
 232         drainers.shutdown();
 233         if (!drainers.awaitTermination(LONG_DELAY_MS, MILLISECONDS)) {
 234             drainers.shutdownNow(); // last resort
 235             throw new AssertionError("thread pool did not terminate");
 236         }
 237     }
 238 
 239     //----------------------------------------------------------------
 240     // The main class of the child process.
 241     // Job's job is to:
 242     // - provide the name of a class to check for leaks.
 243     // - call rendezvousChild exactly twice, while quiescent.
 244     // - in between calls to rendezvousChild, run code that may leak.
 245     //----------------------------------------------------------------
 246     public static class Job {
 247         static String classToCheckForLeaks() {
 248             return
 249                 "java.util.concurrent.locks.AbstractQueuedSynchronizer$Node";
 250         }
 251 
 252         public static void main(String[] args) throws Throwable {
 253             // Synchronize with parent process, so that jps can find us
 254             sendByte(System.out);
 255             System.in.read();
 256 
 257             final ReentrantLock lock = new ReentrantLock();
 258             lock.lock();
 259 
 260             final ReentrantReadWriteLock rwlock
 261                 = new ReentrantReadWriteLock();
 262             final ReentrantReadWriteLock.ReadLock readLock
 263                 = rwlock.readLock();
 264             final ReentrantReadWriteLock.WriteLock writeLock
 265                 = rwlock.writeLock();
 266             rwlock.writeLock().lock();
 267 
 268             final BlockingQueue<Object> q = new LinkedBlockingQueue<>();
 269             final Semaphore fairSem = new Semaphore(0, true);
 270             final Semaphore unfairSem = new Semaphore(0, false);
 271             //final int threads =
 272             //rnd.nextInt(Runtime.getRuntime().availableProcessors() + 1) + 1;
 273             final int threads = 3;
 274             // On Linux, this test runs very slowly for some reason,
 275             // so use a smaller number of iterations.
 276             // Solaris can handle 1 << 18.
 277             // On the other hand, jmap is much slower on Solaris...
 278             final int iterations = 1 << 8;
 279             final CyclicBarrier cb = new CyclicBarrier(threads+1);
 280 
 281             for (int i = 0; i < threads; i++)
 282                 new Thread() { public void run() {
 283                     try {
 284                         final ThreadLocalRandom rnd = ThreadLocalRandom.current();
 285                         for (int j = 0; j < iterations; j++) {
 286                             if (j == iterations/10 || j == iterations - 1) {
 287                                 cb.await(); // Quiesce
 288                                 cb.await(); // Resume
 289                             }
 290                             //int t = rnd.nextInt(2000);
 291                             int t = rnd.nextInt(900);
 292                             check(! lock.tryLock(t, NANOSECONDS));
 293                             check(! readLock.tryLock(t, NANOSECONDS));
 294                             check(! writeLock.tryLock(t, NANOSECONDS));
 295                             equal(null, q.poll(t, NANOSECONDS));
 296                             check(! fairSem.tryAcquire(t, NANOSECONDS));
 297                             check(! unfairSem.tryAcquire(t, NANOSECONDS));
 298                         }
 299                     } catch (Throwable t) { unexpected(t); }
 300                 }}.start();
 301 
 302             cb.await();         // Quiesce
 303             rendezvousChild();  // Measure
 304             cb.await();         // Resume
 305 
 306             cb.await();         // Quiesce
 307             rendezvousChild();  // Measure
 308             cb.await();         // Resume
 309 
 310             System.exit(failed);
 311         }
 312 
 313         // If something goes wrong, we might never see it, since IO
 314         // streams are connected to the parent.  So we need a special
 315         // purpose print method to debug Jobs.
 316         static void debugPrintf(String format, Object... args) {
 317             try {
 318                 new PrintStream(new FileOutputStream("/dev/tty"))
 319                     .printf(format, args);
 320             } catch (Throwable t) { throw new Error(t); }
 321         }
 322     }
 323 
 324     //--------------------- Infrastructure ---------------------------
 325     static volatile int passed = 0, failed = 0;
 326     static void pass() {passed++;}
 327     static void fail() {failed++; Thread.dumpStack();}
 328     static void fail(String msg) {System.out.println(msg); fail();}
 329     static void unexpected(Throwable t) {failed++; t.printStackTrace();}
 330     static void check(boolean cond) {if (cond) pass(); else fail();}
 331     static void check(boolean cond, String m) {if (cond) pass(); else fail(m);}
 332     static void equal(Object x, Object y) {
 333         if (x == null ? y == null : x.equals(y)) pass();
 334         else fail(x + " not equal to " + y);}
 335     public static void main(String[] args) throws Throwable {
 336         try {realMain(args);} catch (Throwable t) {unexpected(t);}
 337         System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
 338         if (failed > 0) throw new AssertionError("Some tests failed");}
 339 }