1 /* 2 * Copyright (c) 2007, 2010, 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 * @author Martin Buchholz 29 */ 30 31 import java.util.*; 32 import java.util.regex.*; 33 import java.util.concurrent.*; 34 import java.util.concurrent.locks.*; 35 import static java.util.concurrent.TimeUnit.*; 36 import java.io.*; 37 38 public class TimedAcquireLeak { 39 static String javahome() { 40 String jh = System.getProperty("java.home"); 41 return (jh.endsWith("jre")) ? jh.substring(0, jh.length() - 4) : jh; 42 } 43 44 static final File bin = new File(javahome(), "bin"); 45 46 static String javaProgramPath(String programName) { 47 return new File(bin, programName).getPath(); 48 } 49 50 static final String java = javaProgramPath("java"); 51 static final String jmap = javaProgramPath("jmap"); 52 static final String jps = javaProgramPath("jps"); 53 54 static String outputOf(Reader r) throws IOException { 55 final StringBuilder sb = new StringBuilder(); 56 final char[] buf = new char[1024]; 57 int n; 58 while ((n = r.read(buf)) > 0) 59 sb.append(buf, 0, n); 60 return sb.toString(); 61 } 62 63 static String outputOf(InputStream is) throws IOException { 64 return outputOf(new InputStreamReader(is, "UTF-8")); 65 } 66 67 static final ExecutorService drainers = Executors.newFixedThreadPool(12); 68 static Future<String> futureOutputOf(final InputStream is) { 69 return drainers.submit( 70 new Callable<String>() { public String call() throws IOException { 71 return outputOf(is); }});} 72 73 static String outputOf(final Process p) { 74 try { 75 Future<String> outputFuture = futureOutputOf(p.getInputStream()); 76 Future<String> errorFuture = futureOutputOf(p.getErrorStream()); 77 final String output = outputFuture.get(); 78 final String error = errorFuture.get(); 79 // Check for successful process completion 80 equal(error, ""); 81 equal(p.waitFor(), 0); 82 equal(p.exitValue(), 0); 83 return output; 84 } catch (Throwable t) { unexpected(t); throw new Error(t); } 85 } 86 87 static String commandOutputOf(String... cmd) { 88 try { return outputOf(new ProcessBuilder(cmd).start()); } 89 catch (Throwable t) { unexpected(t); throw new Error(t); } 90 } 91 92 // To be called exactly twice by the parent process 93 static <T> T rendezvousParent(Process p, 94 Callable<T> callable) throws Throwable { 95 p.getInputStream().read(); 96 T result = callable.call(); 97 OutputStream os = p.getOutputStream(); 98 os.write((byte)'\n'); os.flush(); 99 return result; 100 } 101 102 // To be called exactly twice by the child process 103 public static void rendezvousChild() { 104 try { 105 for (int i = 0; i < 100; i++) { 106 System.gc(); System.runFinalization(); Thread.sleep(50); 107 } 108 System.out.write((byte)'\n'); System.out.flush(); 109 System.in.read(); 110 } catch (Throwable t) { throw new Error(t); } 111 } 112 113 static String match(String s, String regex, int group) { 114 Matcher matcher = Pattern.compile(regex).matcher(s); 115 matcher.find(); 116 return matcher.group(group); 117 } 118 119 static int objectsInUse(final Process child, 120 final String childPid, 121 final String className) { 122 final String regex = 123 "(?m)^ *[0-9]+: +([0-9]+) +[0-9]+ +\\Q"+className+"\\E$"; 124 final Callable<Integer> objectsInUse = 125 new Callable<Integer>() { public Integer call() { 126 Integer i = Integer.parseInt( 127 match(commandOutputOf(jmap, "-histo:live", childPid), 128 regex, 1)); 129 if (i > 100) 130 System.out.print( 131 commandOutputOf(jmap, 132 "-dump:file=dump,format=b", 133 childPid)); 134 return i; 135 }}; 136 try { return rendezvousParent(child, objectsInUse); } 137 catch (Throwable t) { unexpected(t); return -1; } 138 } 139 140 static void realMain(String[] args) throws Throwable { 141 // jmap doesn't work on Windows 142 if (System.getProperty("os.name").startsWith("Windows")) 143 return; 144 145 final String childClassName = Job.class.getName(); 146 final String classToCheckForLeaks = Job.classToCheckForLeaks(); 147 final String uniqueID = 148 String.valueOf(new Random().nextInt(Integer.MAX_VALUE)); 149 150 final String[] jobCmd = { 151 java, "-Xmx8m", "-XX:+UsePerfData", 152 "-classpath", System.getProperty("test.classes", "."), 153 childClassName, uniqueID 154 }; 155 final Process p = new ProcessBuilder(jobCmd).start(); 156 157 final String childPid = 158 match(commandOutputOf(jps, "-m"), 159 "(?m)^ *([0-9]+) +\\Q"+childClassName+"\\E *"+uniqueID+"$", 1); 160 161 final int n0 = objectsInUse(p, childPid, classToCheckForLeaks); 162 final int n1 = objectsInUse(p, childPid, classToCheckForLeaks); 163 equal(p.waitFor(), 0); 164 equal(p.exitValue(), 0); 165 failed += p.exitValue(); 166 167 // Check that no objects were leaked. 168 System.out.printf("%d -> %d%n", n0, n1); 169 check(Math.abs(n1 - n0) < 2); // Almost always n0 == n1 170 check(n1 < 20); 171 drainers.shutdown(); 172 } 173 174 //---------------------------------------------------------------- 175 // The main class of the child process. 176 // Job's job is to: 177 // - provide the name of a class to check for leaks. 178 // - call rendezvousChild exactly twice, while quiescent. 179 // - in between calls to rendezvousChild, run code that may leak. 180 //---------------------------------------------------------------- 181 public static class Job { 182 static String classToCheckForLeaks() { 183 return 184 "java.util.concurrent.locks.AbstractQueuedSynchronizer$Node"; 185 } 186 187 public static void main(String[] args) throws Throwable { 188 final ReentrantLock lock = new ReentrantLock(); 189 lock.lock(); 190 191 final ReentrantReadWriteLock rwlock 192 = new ReentrantReadWriteLock(); 193 final ReentrantReadWriteLock.ReadLock readLock 194 = rwlock.readLock(); 195 final ReentrantReadWriteLock.WriteLock writeLock 196 = rwlock.writeLock(); 197 rwlock.writeLock().lock(); 198 199 final BlockingQueue<Object> q = new LinkedBlockingQueue<Object>(); 200 final Semaphore fairSem = new Semaphore(0, true); 201 final Semaphore unfairSem = new Semaphore(0, false); 202 //final int threads = 203 //rnd.nextInt(Runtime.getRuntime().availableProcessors() + 1) + 1; 204 final int threads = 3; 205 // On Linux, this test runs very slowly for some reason, 206 // so use a smaller number of iterations. 207 // Solaris can handle 1 << 18. 208 // On the other hand, jmap is much slower on Solaris... 209 final int iterations = 1 << 8; 210 final CyclicBarrier cb = new CyclicBarrier(threads+1); 211 212 for (int i = 0; i < threads; i++) 213 new Thread() { public void run() { 214 try { 215 final Random rnd = new Random(); 216 for (int j = 0; j < iterations; j++) { 217 if (j == iterations/10 || j == iterations - 1) { 218 cb.await(); // Quiesce 219 cb.await(); // Resume 220 } 221 //int t = rnd.nextInt(2000); 222 int t = rnd.nextInt(900); 223 check(! lock.tryLock(t, NANOSECONDS)); 224 check(! readLock.tryLock(t, NANOSECONDS)); 225 check(! writeLock.tryLock(t, NANOSECONDS)); 226 equal(null, q.poll(t, NANOSECONDS)); 227 check(! fairSem.tryAcquire(t, NANOSECONDS)); 228 check(! unfairSem.tryAcquire(t, NANOSECONDS)); 229 } 230 } catch (Throwable t) { unexpected(t); } 231 }}.start(); 232 233 cb.await(); // Quiesce 234 rendezvousChild(); // Measure 235 cb.await(); // Resume 236 237 cb.await(); // Quiesce 238 rendezvousChild(); // Measure 239 cb.await(); // Resume 240 241 System.exit(failed); 242 } 243 244 // If something goes wrong, we might never see it, since IO 245 // streams are connected to the parent. So we need a special 246 // purpose print method to debug Jobs. 247 static void debugPrintf(String format, Object... args) { 248 try { 249 new PrintStream(new FileOutputStream("/dev/tty")) 250 .printf(format, args); 251 } catch (Throwable t) { throw new Error(t); } 252 } 253 } 254 255 //--------------------- Infrastructure --------------------------- 256 static volatile int passed = 0, failed = 0; 257 static void pass() {passed++;} 258 static void fail() {failed++; Thread.dumpStack();} 259 static void fail(String msg) {System.out.println(msg); fail();} 260 static void unexpected(Throwable t) {failed++; t.printStackTrace();} 261 static void check(boolean cond) {if (cond) pass(); else fail();} 262 static void check(boolean cond, String m) {if (cond) pass(); else fail(m);} 263 static void equal(Object x, Object y) { 264 if (x == null ? y == null : x.equals(y)) pass(); 265 else fail(x + " not equal to " + y);} 266 public static void main(String[] args) throws Throwable { 267 try {realMain(args);} catch (Throwable t) {unexpected(t);} 268 System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); 269 if (failed > 0) throw new AssertionError("Some tests failed");} 270 }