jdk/test/java/util/concurrent/locks/Lock/TimedAcquireLeak.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/concurrent/locks/Lock/TimedAcquireLeak.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6460501 6236036 6500694 6490770
+ * @summary Repeated failed timed waits shouldn't leak memory
+ * @author Martin Buchholz
+ */
+
+import java.util.*;
+import java.util.regex.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+import static java.util.concurrent.TimeUnit.*;
+import java.io.*;
+
+public class TimedAcquireLeak {
+    static String javahome() {
+        String jh = System.getProperty("java.home");
+        return (jh.endsWith("jre")) ? jh.substring(0, jh.length() - 4) : jh;
+    }
+
+    static final File bin = new File(javahome(), "bin");
+
+    static String javaProgramPath(String programName) {
+        return new File(bin, programName).getPath();
+    }
+
+    static final String java = javaProgramPath("java");
+    static final String jmap = javaProgramPath("jmap");
+    static final String jps  = javaProgramPath("jps");
+
+    static String outputOf(Reader r) throws IOException {
+        final StringBuilder sb = new StringBuilder();
+        final char[] buf = new char[1024];
+        int n;
+        while ((n = r.read(buf)) > 0)
+            sb.append(buf, 0, n);
+        return sb.toString();
+    }
+
+    static String outputOf(InputStream is) throws IOException {
+        return outputOf(new InputStreamReader(is, "UTF-8"));
+    }
+
+    final static ExecutorService drainers = Executors.newFixedThreadPool(12);
+    static Future<String> futureOutputOf(final InputStream is) {
+        return drainers.submit(
+            new Callable<String>() { public String call() throws IOException {
+                    return outputOf(is); }});}
+
+    static String outputOf(final Process p) {
+        try {
+            Future<String> outputFuture = futureOutputOf(p.getInputStream());
+            Future<String> errorFuture = futureOutputOf(p.getErrorStream());
+            final String output = outputFuture.get();
+            final String error = errorFuture.get();
+            // Check for successful process completion
+            equal(error, "");
+            equal(p.waitFor(), 0);
+            equal(p.exitValue(), 0);
+            return output;
+        } catch (Throwable t) { unexpected(t); throw new Error(t); }
+    }
+
+    static String commandOutputOf(String... cmd) {
+        try { return outputOf(new ProcessBuilder(cmd).start()); }
+        catch (Throwable t) { unexpected(t); throw new Error(t); }
+    }
+
+    // To be called exactly twice by the parent process
+    static <T> T rendezvousParent(Process p,
+                                  Callable<T> callable) throws Throwable {
+        p.getInputStream().read();
+        T result = callable.call();
+        OutputStream os = p.getOutputStream();
+        os.write((byte)'\n'); os.flush();
+        return result;
+    }
+
+    // To be called exactly twice by the child process
+    public static void rendezvousChild() {
+        try {
+            for (int i = 0; i < 100; i++) {
+                System.gc(); System.runFinalization(); Thread.sleep(50);
+            }
+            System.out.write((byte)'\n'); System.out.flush();
+            System.in.read();
+        } catch (Throwable t) { throw new Error(t); }
+    }
+
+    static String match(String s, String regex, int group) {
+        Matcher matcher = Pattern.compile(regex).matcher(s);
+        matcher.find();
+        return matcher.group(group);
+    }
+
+    static int objectsInUse(final Process child,
+                            final String childPid,
+                            final String className) {
+        final String regex =
+            "(?m)^ *[0-9]+: +([0-9]+) +[0-9]+ +\\Q"+className+"\\E$";
+        final Callable<Integer> objectsInUse =
+            new Callable<Integer>() { public Integer call() {
+                Integer i = Integer.parseInt(
+                    match(commandOutputOf(jmap, "-histo:live", childPid),
+                          regex, 1));
+                if (i > 100)
+                    System.out.print(
+                        commandOutputOf(jmap,
+                                        "-dump:file=dump,format=b",
+                                        childPid));
+                return i;
+            }};
+        try { return rendezvousParent(child, objectsInUse); }
+        catch (Throwable t) { unexpected(t); return -1; }
+    }
+
+    static void realMain(String[] args) throws Throwable {
+        // jmap doesn't work on Windows
+        if (System.getProperty("os.name").startsWith("Windows"))
+            return;
+
+        final String childClassName = Job.class.getName();
+        final String classToCheckForLeaks = Job.classToCheckForLeaks();
+        final String uniqueID =
+            String.valueOf(new Random().nextInt(Integer.MAX_VALUE));
+
+        final String[] jobCmd = {
+            java, "-Xmx8m",
+            "-classpath", System.getProperty("test.classes", "."),
+            childClassName, uniqueID
+        };
+        final Process p = new ProcessBuilder(jobCmd).start();
+
+        final String childPid =
+            match(commandOutputOf(jps, "-m"),
+                  "(?m)^ *([0-9]+) +\\Q"+childClassName+"\\E *"+uniqueID+"$", 1);
+
+        final int n0 = objectsInUse(p, childPid, classToCheckForLeaks);
+        final int n1 = objectsInUse(p, childPid, classToCheckForLeaks);
+        equal(p.waitFor(), 0);
+        equal(p.exitValue(), 0);
+        failed += p.exitValue();
+
+        // Check that no objects were leaked.
+        System.out.printf("%d -> %d%n", n0, n1);
+        check(Math.abs(n1 - n0) < 2); // Almost always n0 == n1
+        check(n1 < 20);
+        drainers.shutdown();
+    }
+
+    //----------------------------------------------------------------
+    // The main class of the child process.
+    // Job's job is to:
+    // - provide the name of a class to check for leaks.
+    // - call rendezvousChild exactly twice, while quiescent.
+    // - in between calls to rendezvousChild, run code that may leak.
+    //----------------------------------------------------------------
+    public static class Job {
+        static String classToCheckForLeaks() {
+            return
+                "java.util.concurrent.locks.AbstractQueuedSynchronizer$Node";
+        }
+
+        public static void main(String[] args) throws Throwable {
+            final ReentrantLock lock = new ReentrantLock();
+            lock.lock();
+
+            final ReentrantReadWriteLock rwlock
+                = new ReentrantReadWriteLock();
+            final ReentrantReadWriteLock.ReadLock readLock
+                = rwlock.readLock();
+            final ReentrantReadWriteLock.WriteLock writeLock
+                = rwlock.writeLock();
+            rwlock.writeLock().lock();
+
+            final BlockingQueue<Object> q = new LinkedBlockingQueue<Object>();
+            final Semaphore fairSem = new Semaphore(0, true);
+            final Semaphore unfairSem = new Semaphore(0, false);
+            //final int threads =
+            //rnd.nextInt(Runtime.getRuntime().availableProcessors() + 1) + 1;
+            final int threads = 3;
+            // On Linux, this test runs very slowly for some reason,
+            // so use a smaller number of iterations.
+            // Solaris can handle 1 << 18.
+            // On the other hand, jmap is much slower on Solaris...
+            final int iterations = 1 << 8;
+            final CyclicBarrier cb = new CyclicBarrier(threads+1);
+
+            for (int i = 0; i < threads; i++)
+                new Thread() { public void run() {
+                    try {
+                        final Random rnd = new Random();
+                        for (int j = 0; j < iterations; j++) {
+                            if (j == iterations/10 || j == iterations - 1) {
+                                cb.await(); // Quiesce
+                                cb.await(); // Resume
+                            }
+                            //int t = rnd.nextInt(2000);
+                            int t = rnd.nextInt(900);
+                            check(! lock.tryLock(t, NANOSECONDS));
+                            check(! readLock.tryLock(t, NANOSECONDS));
+                            check(! writeLock.tryLock(t, NANOSECONDS));
+                            equal(null, q.poll(t, NANOSECONDS));
+                            check(! fairSem.tryAcquire(t, NANOSECONDS));
+                            check(! unfairSem.tryAcquire(t, NANOSECONDS));
+                        }
+                    } catch (Throwable t) { unexpected(t); }
+                }}.start();
+
+            cb.await();         // Quiesce
+            rendezvousChild();  // Measure
+            cb.await();         // Resume
+
+            cb.await();         // Quiesce
+            rendezvousChild();  // Measure
+            cb.await();         // Resume
+
+            System.exit(failed);
+        }
+
+        // If something goes wrong, we might never see it, since IO
+        // streams are connected to the parent.  So we need a special
+        // purpose print method to debug Jobs.
+        static void debugPrintf(String format, Object... args) {
+            try {
+                new PrintStream(new FileOutputStream("/dev/tty"))
+                    .printf(format, args);
+            } catch (Throwable t) { throw new Error(t); }
+        }
+    }
+
+    //--------------------- Infrastructure ---------------------------
+    static volatile int passed = 0, failed = 0;
+    static void pass() {passed++;}
+    static void fail() {failed++; Thread.dumpStack();}
+    static void fail(String msg) {System.out.println(msg); fail();}
+    static void unexpected(Throwable t) {failed++; t.printStackTrace();}
+    static void check(boolean cond) {if (cond) pass(); else fail();}
+    static void check(boolean cond, String m) {if (cond) pass(); else fail(m);}
+    static void equal(Object x, Object y) {
+        if (x == null ? y == null : x.equals(y)) pass();
+        else fail(x + " not equal to " + y);}
+    public static void main(String[] args) throws Throwable {
+        try {realMain(args);} catch (Throwable t) {unexpected(t);}
+        System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
+        if (failed > 0) throw new AssertionError("Some tests failed");}
+}