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 /lib/testlibrary/ 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.testlibrary.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 }