1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import dalvik.system.VMRuntime; 18 19 import java.lang.reflect.*; 20 import java.util.ArrayList; 21 import java.util.Arrays; 22 import java.util.Collections; 23 import java.util.HashMap; 24 import java.util.HashSet; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Set; 28 import java.util.concurrent.Semaphore; 29 30 // Run on host with: 31 // javac ThreadTest.java && java ThreadStress && rm *.class 32 // Through run-test: 33 // test/run-test {run-test-args} 004-ThreadStress [Main {ThreadStress-args}] 34 // (It is important to pass Main if you want to give parameters...) 35 // 36 // ThreadStress command line parameters: 37 // -n X .............. number of threads 38 // -d X .............. number of daemon threads 39 // -o X .............. number of overall operations 40 // -t X .............. number of operations per thread 41 // -p X .............. number of permits granted by semaphore 42 // --dumpmap ......... print the frequency map 43 // --locks-only ...... select a pre-set frequency map with lock-related operations only 44 // --allocs-only ..... select a pre-set frequency map with allocation-related operations only 45 // -oom:X ............ frequency of OOM (double) 46 // -sigquit:X ........ frequency of SigQuit (double) 47 // -alloc:X .......... frequency of Alloc (double) 48 // -largealloc:X ..... frequency of LargeAlloc (double) 49 // -nonmovingalloc:X.. frequency of NonMovingAlloc (double) 50 // -stacktrace:X ..... frequency of StackTrace (double) 51 // -exit:X ........... frequency of Exit (double) 52 // -sleep:X .......... frequency of Sleep (double) 53 // -wait:X ........... frequency of Wait (double) 54 // -timedwait:X ...... frequency of TimedWait (double) 55 // -syncandwork:X .... frequency of SyncAndWork (double) 56 // -queuedwait:X ..... frequency of QueuedWait (double) 57 58 public class Main implements Runnable { 59 60 public static final boolean DEBUG = false; 61 62 private static abstract class Operation { 63 /** 64 * Perform the action represented by this operation. Returns true if the thread should 65 * continue when executed by a runner (non-daemon) thread. 66 */ perform()67 public abstract boolean perform(); 68 } 69 70 private final static class OOM extends Operation { 71 private final static int ALLOC_SIZE = 1024; 72 73 @Override perform()74 public boolean perform() { 75 try { 76 List<byte[]> l = new ArrayList<byte[]>(); 77 while (true) { 78 l.add(new byte[ALLOC_SIZE]); 79 } 80 } catch (OutOfMemoryError e) { 81 } 82 return true; 83 } 84 } 85 86 private final static class SigQuit extends Operation { 87 private final static int sigquit; 88 private final static Method kill; 89 private final static int pid; 90 91 static { 92 int pidTemp = -1; 93 int sigquitTemp = -1; 94 Method killTemp = null; 95 96 try { 97 Class<?> osClass = Class.forName("android.system.Os"); 98 Method getpid = osClass.getDeclaredMethod("getpid"); 99 pidTemp = (Integer)getpid.invoke(null); 100 101 Class<?> osConstants = Class.forName("android.system.OsConstants"); 102 Field sigquitField = osConstants.getDeclaredField("SIGQUIT"); 103 sigquitTemp = (Integer)sigquitField.get(null); 104 105 killTemp = osClass.getDeclaredMethod("kill", int.class, int.class); 106 } catch (Exception e) { 107 Main.printThrowable(e); 108 } 109 110 pid = pidTemp; 111 sigquit = sigquitTemp; 112 kill = killTemp; 113 } 114 115 @Override perform()116 public boolean perform() { 117 try { 118 kill.invoke(null, pid, sigquit); 119 } catch (OutOfMemoryError e) { 120 } catch (Exception e) { 121 if (!e.getClass().getName().equals(Main.errnoExceptionName)) { 122 Main.printThrowable(e); 123 } 124 } 125 return true; 126 } 127 } 128 129 private final static class Alloc extends Operation { 130 private final static int ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS. 131 private final static int ALLOC_COUNT = 1024; 132 133 @Override perform()134 public boolean perform() { 135 try { 136 List<byte[]> l = new ArrayList<byte[]>(); 137 for (int i = 0; i < ALLOC_COUNT; i++) { 138 l.add(new byte[ALLOC_SIZE]); 139 } 140 } catch (OutOfMemoryError e) { 141 } 142 return true; 143 } 144 } 145 146 private final static class LargeAlloc extends Operation { 147 private final static int PAGE_SIZE = 4096; 148 private final static int PAGE_SIZE_MODIFIER = 10; // Needs to be large enough for LOS. 149 private final static int ALLOC_COUNT = 100; 150 151 @Override perform()152 public boolean perform() { 153 try { 154 List<byte[]> l = new ArrayList<byte[]>(); 155 for (int i = 0; i < ALLOC_COUNT; i++) { 156 l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]); 157 } 158 } catch (OutOfMemoryError e) { 159 } 160 return true; 161 } 162 } 163 164 private final static class NonMovingAlloc extends Operation { 165 private final static int ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS. 166 private final static int ALLOC_COUNT = 1024; 167 private final static VMRuntime runtime = VMRuntime.getRuntime(); 168 169 @Override perform()170 public boolean perform() { 171 try { 172 List<byte[]> l = new ArrayList<byte[]>(); 173 for (int i = 0; i < ALLOC_COUNT; i++) { 174 l.add((byte[]) runtime.newNonMovableArray(byte.class, ALLOC_SIZE)); 175 } 176 } catch (OutOfMemoryError e) { 177 } 178 return true; 179 } 180 } 181 182 183 private final static class StackTrace extends Operation { 184 @Override perform()185 public boolean perform() { 186 try { 187 Thread.currentThread().getStackTrace(); 188 } catch (OutOfMemoryError e) { 189 } 190 return true; 191 } 192 } 193 194 private final static class Exit extends Operation { 195 @Override perform()196 public boolean perform() { 197 return false; 198 } 199 } 200 201 private final static class Sleep extends Operation { 202 private final static int SLEEP_TIME = 100; 203 204 @Override perform()205 public boolean perform() { 206 try { 207 Thread.sleep(SLEEP_TIME); 208 } catch (InterruptedException ignored) { 209 } 210 return true; 211 } 212 } 213 214 private final static class TimedWait extends Operation { 215 private final static int SLEEP_TIME = 100; 216 217 private final Object lock; 218 TimedWait(Object lock)219 public TimedWait(Object lock) { 220 this.lock = lock; 221 } 222 223 @Override perform()224 public boolean perform() { 225 synchronized (lock) { 226 try { 227 lock.wait(SLEEP_TIME, 0); 228 } catch (InterruptedException ignored) { 229 } 230 } 231 return true; 232 } 233 } 234 235 private final static class Wait extends Operation { 236 private final Object lock; 237 Wait(Object lock)238 public Wait(Object lock) { 239 this.lock = lock; 240 } 241 242 @Override perform()243 public boolean perform() { 244 synchronized (lock) { 245 try { 246 lock.wait(); 247 } catch (InterruptedException ignored) { 248 } 249 } 250 return true; 251 } 252 } 253 254 private final static class SyncAndWork extends Operation { 255 private final Object lock; 256 SyncAndWork(Object lock)257 public SyncAndWork(Object lock) { 258 this.lock = lock; 259 } 260 261 @Override perform()262 public boolean perform() { 263 synchronized (lock) { 264 try { 265 Thread.sleep((int)(Math.random() * 50 + 50)); 266 } catch (InterruptedException ignored) { 267 } 268 } 269 return true; 270 } 271 } 272 273 // An operation requiring the acquisition of a permit from a semaphore 274 // for its execution. This operation has been added to exercise 275 // java.util.concurrent.locks.AbstractQueuedSynchronizer, used in the 276 // implementation of java.util.concurrent.Semaphore. We use the latter, 277 // as the former is not supposed to be used directly (see b/63822989). 278 private final static class QueuedWait extends Operation { 279 private final static int SLEEP_TIME = 100; 280 281 private final Semaphore semaphore; 282 QueuedWait(Semaphore semaphore)283 public QueuedWait(Semaphore semaphore) { 284 this.semaphore = semaphore; 285 } 286 287 @Override perform()288 public boolean perform() { 289 boolean permitAcquired = false; 290 try { 291 semaphore.acquire(); 292 permitAcquired = true; 293 Thread.sleep(SLEEP_TIME); 294 } catch (OutOfMemoryError ignored) { 295 // The call to semaphore.acquire() above may trigger an OOME, 296 // despite the care taken doing some warm-up by forcing 297 // ahead-of-time initialization of classes used by the Semaphore 298 // class (see forceTransitiveClassInitialization below). 299 // For instance, one of the code paths executes 300 // AbstractQueuedSynchronizer.addWaiter, which allocates an 301 // AbstractQueuedSynchronizer$Node (see b/67730573). 302 // In that case, just ignore the OOME and continue. 303 } catch (InterruptedException ignored) { 304 } finally { 305 if (permitAcquired) { 306 semaphore.release(); 307 } 308 } 309 return true; 310 } 311 } 312 createDefaultFrequencyMap(Object lock, Semaphore semaphore)313 private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock, 314 Semaphore semaphore) { 315 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); 316 frequencyMap.put(new OOM(), 0.005); // 1/200 317 frequencyMap.put(new SigQuit(), 0.095); // 19/200 318 frequencyMap.put(new Alloc(), 0.225); // 45/200 319 frequencyMap.put(new LargeAlloc(), 0.05); // 10/200 320 // TODO: NonMovingAlloc operations fail an assertion with the 321 // GSS collector (see b/72738921); disable them for now. 322 frequencyMap.put(new NonMovingAlloc(), 0.0); // 0/200 323 frequencyMap.put(new StackTrace(), 0.1); // 20/200 324 frequencyMap.put(new Exit(), 0.225); // 45/200 325 frequencyMap.put(new Sleep(), 0.125); // 25/200 326 frequencyMap.put(new TimedWait(lock), 0.05); // 10/200 327 frequencyMap.put(new Wait(lock), 0.075); // 15/200 328 frequencyMap.put(new QueuedWait(semaphore), 0.05); // 10/200 329 330 return frequencyMap; 331 } 332 createAllocFrequencyMap()333 private final static Map<Operation, Double> createAllocFrequencyMap() { 334 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); 335 frequencyMap.put(new Sleep(), 0.2); // 40/200 336 frequencyMap.put(new Alloc(), 0.575); // 115/200 337 frequencyMap.put(new LargeAlloc(), 0.15); // 30/200 338 frequencyMap.put(new NonMovingAlloc(), 0.075); // 15/200 339 340 return frequencyMap; 341 } 342 createLockFrequencyMap(Object lock)343 private final static Map<Operation, Double> createLockFrequencyMap(Object lock) { 344 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); 345 frequencyMap.put(new Sleep(), 0.2); // 40/200 346 frequencyMap.put(new TimedWait(lock), 0.2); // 40/200 347 frequencyMap.put(new Wait(lock), 0.2); // 40/200 348 frequencyMap.put(new SyncAndWork(lock), 0.4); // 80/200 349 350 return frequencyMap; 351 } 352 main(String[] args)353 public static void main(String[] args) throws Exception { 354 System.loadLibrary(args[0]); 355 parseAndRun(args); 356 } 357 updateFrequencyMap(Map<Operation, Double> in, Object lock, Semaphore semaphore, String arg)358 private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in, 359 Object lock, Semaphore semaphore, String arg) { 360 String split[] = arg.split(":"); 361 if (split.length != 2) { 362 throw new IllegalArgumentException("Can't split argument " + arg); 363 } 364 double d; 365 try { 366 d = Double.parseDouble(split[1]); 367 } catch (Exception e) { 368 throw new IllegalArgumentException(e); 369 } 370 if (d < 0) { 371 throw new IllegalArgumentException(arg + ": value must be >= 0."); 372 } 373 Operation op = null; 374 if (split[0].equals("-oom")) { 375 op = new OOM(); 376 } else if (split[0].equals("-sigquit")) { 377 op = new SigQuit(); 378 } else if (split[0].equals("-alloc")) { 379 op = new Alloc(); 380 } else if (split[0].equals("-largealloc")) { 381 op = new LargeAlloc(); 382 } else if (split[0].equals("-stacktrace")) { 383 op = new StackTrace(); 384 } else if (split[0].equals("-exit")) { 385 op = new Exit(); 386 } else if (split[0].equals("-sleep")) { 387 op = new Sleep(); 388 } else if (split[0].equals("-wait")) { 389 op = new Wait(lock); 390 } else if (split[0].equals("-timedwait")) { 391 op = new TimedWait(lock); 392 } else if (split[0].equals("-syncandwork")) { 393 op = new SyncAndWork(lock); 394 } else if (split[0].equals("-queuedwait")) { 395 op = new QueuedWait(semaphore); 396 } else { 397 throw new IllegalArgumentException("Unknown arg " + arg); 398 } 399 400 if (in == null) { 401 in = new HashMap<Operation, Double>(); 402 } 403 in.put(op, d); 404 405 return in; 406 } 407 normalize(Map<Operation, Double> map)408 private static void normalize(Map<Operation, Double> map) { 409 double sum = 0; 410 for (Double d : map.values()) { 411 sum += d; 412 } 413 if (sum == 0) { 414 throw new RuntimeException("No elements!"); 415 } 416 if (sum != 1.0) { 417 // Avoid ConcurrentModificationException. 418 Set<Operation> tmp = new HashSet<>(map.keySet()); 419 for (Operation op : tmp) { 420 map.put(op, map.get(op) / sum); 421 } 422 } 423 } 424 parseAndRun(String[] args)425 public static void parseAndRun(String[] args) throws Exception { 426 int numberOfThreads = -1; 427 int numberOfDaemons = -1; 428 int totalOperations = -1; 429 int operationsPerThread = -1; 430 int permits = -1; 431 Object lock = new Object(); 432 Map<Operation, Double> frequencyMap = null; 433 boolean dumpMap = false; 434 435 if (args != null) { 436 // args[0] is libarttest 437 for (int i = 1; i < args.length; i++) { 438 if (args[i].equals("-n")) { 439 i++; 440 numberOfThreads = Integer.parseInt(args[i]); 441 } else if (args[i].equals("-d")) { 442 i++; 443 numberOfDaemons = Integer.parseInt(args[i]); 444 } else if (args[i].equals("-o")) { 445 i++; 446 totalOperations = Integer.parseInt(args[i]); 447 } else if (args[i].equals("-t")) { 448 i++; 449 operationsPerThread = Integer.parseInt(args[i]); 450 } else if (args[i].equals("-p")) { 451 i++; 452 permits = Integer.parseInt(args[i]); 453 } else if (args[i].equals("--locks-only")) { 454 frequencyMap = createLockFrequencyMap(lock); 455 } else if (args[i].equals("--allocs-only")) { 456 frequencyMap = createAllocFrequencyMap(); 457 } else if (args[i].equals("--dumpmap")) { 458 dumpMap = true; 459 } else { 460 // Processing an argument of the form "-<operation>:X" 461 // (where X is a double value). 462 Semaphore semaphore = getSemaphore(permits); 463 frequencyMap = updateFrequencyMap(frequencyMap, lock, semaphore, args[i]); 464 } 465 } 466 } 467 468 if (totalOperations != -1 && operationsPerThread != -1) { 469 throw new IllegalArgumentException( 470 "Specified both totalOperations and operationsPerThread"); 471 } 472 473 if (numberOfThreads == -1) { 474 numberOfThreads = 5; 475 } 476 477 if (numberOfDaemons == -1) { 478 numberOfDaemons = 3; 479 } 480 481 if (totalOperations == -1) { 482 totalOperations = 1000; 483 } 484 485 if (operationsPerThread == -1) { 486 operationsPerThread = totalOperations/numberOfThreads; 487 } 488 489 if (frequencyMap == null) { 490 Semaphore semaphore = getSemaphore(permits); 491 frequencyMap = createDefaultFrequencyMap(lock, semaphore); 492 } 493 normalize(frequencyMap); 494 495 if (dumpMap) { 496 System.out.println(frequencyMap); 497 } 498 499 try { 500 runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap); 501 } catch (Throwable t) { 502 // In this case, the output should not contain all the required 503 // "Finishing worker" lines. 504 Main.printThrowable(t); 505 } 506 } 507 getSemaphore(int permits)508 private static Semaphore getSemaphore(int permits) { 509 if (permits == -1) { 510 // Default number of permits. 511 permits = 3; 512 } 513 514 Semaphore semaphore = new Semaphore(permits, /* fair */ true); 515 forceTransitiveClassInitialization(semaphore, permits); 516 return semaphore; 517 } 518 519 // Force ahead-of-time initialization of classes used by Semaphore 520 // code. Try to exercise all code paths likely to be taken during 521 // the actual test later (including having a thread blocking on 522 // the semaphore trying to acquire a permit), so that we increase 523 // the chances to initialize all classes indirectly used by 524 // QueuedWait (e.g. AbstractQueuedSynchronizer$Node). forceTransitiveClassInitialization(Semaphore semaphore, final int permits)525 private static void forceTransitiveClassInitialization(Semaphore semaphore, final int permits) { 526 // Ensure `semaphore` has the expected number of permits 527 // before we start. 528 assert semaphore.availablePermits() == permits; 529 530 // Let the main (current) thread acquire all permits from 531 // `semaphore`. Then create an auxiliary thread acquiring a 532 // permit from `semaphore`, blocking because none is 533 // available. Have the main thread release one permit, thus 534 // unblocking the second thread. 535 536 // Auxiliary thread. 537 Thread auxThread = new Thread("Aux") { 538 public void run() { 539 try { 540 // Try to acquire one permit, and block until 541 // that permit is released by the main thread. 542 semaphore.acquire(); 543 // When unblocked, release the acquired permit 544 // immediately. 545 semaphore.release(); 546 } catch (InterruptedException ignored) { 547 throw new RuntimeException("Test set up failed in auxiliary thread"); 548 } 549 } 550 }; 551 552 // Main thread. 553 try { 554 // Acquire all permits. 555 semaphore.acquire(permits); 556 // Start the auxiliary thread and have it try to acquire a 557 // permit. 558 auxThread.start(); 559 // Synchronization: Wait until the auxiliary thread is 560 // blocked trying to acquire a permit from `semaphore`. 561 while (!semaphore.hasQueuedThreads()) { 562 Thread.sleep(100); 563 } 564 // Release one permit, thus unblocking `auxThread` and let 565 // it acquire a permit. 566 semaphore.release(); 567 // Synchronization: Wait for the auxiliary thread to die. 568 auxThread.join(); 569 // Release remaining permits. 570 semaphore.release(permits - 1); 571 572 // Verify that all permits have been released. 573 assert semaphore.availablePermits() == permits; 574 } catch (InterruptedException ignored) { 575 throw new RuntimeException("Test set up failed in main thread"); 576 } 577 } 578 runTest(final int numberOfThreads, final int numberOfDaemons, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap)579 public static void runTest(final int numberOfThreads, final int numberOfDaemons, 580 final int operationsPerThread, final Object lock, 581 Map<Operation, Double> frequencyMap) throws Exception { 582 final Thread mainThread = Thread.currentThread(); 583 final Barrier startBarrier = new Barrier(numberOfThreads + numberOfDaemons + 1); 584 585 // Each normal thread is going to do operationsPerThread 586 // operations. Each daemon thread will loop over all 587 // the operations and will not stop. 588 // The distribution of operations is determined by 589 // the frequencyMap values. We fill out an Operation[] 590 // for each thread with the operations it is to perform. The 591 // Operation[] is shuffled so that there is more random 592 // interactions between the threads. 593 594 // Fill in the Operation[] array for each thread by laying 595 // down references to operation according to their desired 596 // frequency. 597 // The first numberOfThreads elements are normal threads, the last 598 // numberOfDaemons elements are daemon threads. 599 final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons]; 600 for (int t = 0; t < threadStresses.length; t++) { 601 Operation[] operations = new Operation[operationsPerThread]; 602 int o = 0; 603 LOOP: 604 while (true) { 605 for (Operation op : frequencyMap.keySet()) { 606 int freq = (int)(frequencyMap.get(op) * operationsPerThread); 607 for (int f = 0; f < freq; f++) { 608 if (o == operations.length) { 609 break LOOP; 610 } 611 operations[o] = op; 612 o++; 613 } 614 } 615 } 616 // Randomize the operation order 617 Collections.shuffle(Arrays.asList(operations)); 618 threadStresses[t] = (t < numberOfThreads) 619 ? new Main(lock, t, operations) 620 : new Daemon(lock, t, operations, mainThread, startBarrier); 621 } 622 623 // Enable to dump operation counts per thread to make sure its 624 // sane compared to frequencyMap. 625 if (DEBUG) { 626 for (int t = 0; t < threadStresses.length; t++) { 627 Operation[] operations = threadStresses[t].operations; 628 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>(); 629 for (Operation operation : operations) { 630 Integer ops = distribution.get(operation); 631 if (ops == null) { 632 ops = 1; 633 } else { 634 ops++; 635 } 636 distribution.put(operation, ops); 637 } 638 System.out.println("Distribution for " + t); 639 for (Operation op : frequencyMap.keySet()) { 640 System.out.println(op + " = " + distribution.get(op)); 641 } 642 } 643 } 644 645 // Create the runners for each thread. The runner Thread 646 // ensures that thread that exit due to operation Exit will be 647 // restarted until they reach their desired 648 // operationsPerThread. 649 Thread[] runners = new Thread[numberOfThreads]; 650 for (int r = 0; r < runners.length; r++) { 651 final Main ts = threadStresses[r]; 652 runners[r] = new Thread("Runner thread " + r) { 653 final Main threadStress = ts; 654 public void run() { 655 try { 656 int id = threadStress.id; 657 // No memory hungry task are running yet, so println() should succeed. 658 System.out.println("Starting worker for " + id); 659 // Wait until all runners and daemons reach the starting point. 660 startBarrier.await(); 661 // Run the stress tasks. 662 while (threadStress.nextOperation < operationsPerThread) { 663 try { 664 Thread thread = new Thread(ts, "Worker thread " + id); 665 thread.start(); 666 thread.join(); 667 668 if (DEBUG) { 669 System.out.println( 670 "Thread exited for " + id + " with " + 671 (operationsPerThread - threadStress.nextOperation) + 672 " operations remaining."); 673 } 674 } catch (OutOfMemoryError e) { 675 // Ignore OOME since we need to print "Finishing worker" 676 // for the test to pass. This OOM can come from creating 677 // the Thread or from the DEBUG output. 678 // Note that the Thread creation may fail repeatedly, 679 // preventing the runner from making any progress, 680 // especially if the number of daemons is too high. 681 } 682 } 683 // Print "Finishing worker" through JNI to avoid OOME. 684 Main.printString(Main.finishingWorkerMessage); 685 } catch (Throwable t) { 686 Main.printThrowable(t); 687 // Interrupt the main thread, so that it can orderly shut down 688 // instead of waiting indefinitely for some Barrier. 689 mainThread.interrupt(); 690 } 691 } 692 }; 693 } 694 695 // The notifier thread is a daemon just loops forever to wake 696 // up threads in operation Wait. 697 if (lock != null) { 698 Thread notifier = new Thread("Notifier") { 699 public void run() { 700 while (true) { 701 synchronized (lock) { 702 lock.notifyAll(); 703 } 704 } 705 } 706 }; 707 notifier.setDaemon(true); 708 notifier.start(); 709 } 710 711 // Create and start the daemon threads. 712 for (int r = 0; r < numberOfDaemons; r++) { 713 Main daemon = threadStresses[numberOfThreads + r]; 714 Thread t = new Thread(daemon, "Daemon thread " + daemon.id); 715 t.setDaemon(true); 716 t.start(); 717 } 718 719 for (int r = 0; r < runners.length; r++) { 720 runners[r].start(); 721 } 722 // Wait for all threads to reach the starting point. 723 startBarrier.await(); 724 // Wait for runners to finish. 725 for (int r = 0; r < runners.length; r++) { 726 runners[r].join(); 727 } 728 } 729 730 protected final Operation[] operations; 731 private final Object lock; 732 protected final int id; 733 734 private int nextOperation; 735 Main(Object lock, int id, Operation[] operations)736 private Main(Object lock, int id, Operation[] operations) { 737 this.lock = lock; 738 this.id = id; 739 this.operations = operations; 740 } 741 run()742 public void run() { 743 try { 744 if (DEBUG) { 745 System.out.println("Starting ThreadStress " + id); 746 } 747 while (nextOperation < operations.length) { 748 Operation operation = operations[nextOperation]; 749 if (DEBUG) { 750 System.out.println("ThreadStress " + id 751 + " operation " + nextOperation 752 + " is " + operation); 753 } 754 nextOperation++; 755 if (!operation.perform()) { 756 return; 757 } 758 } 759 } finally { 760 if (DEBUG) { 761 System.out.println("Finishing ThreadStress for " + id); 762 } 763 } 764 } 765 766 private static class Daemon extends Main { Daemon(Object lock, int id, Operation[] operations, Thread mainThread, Barrier startBarrier)767 private Daemon(Object lock, 768 int id, 769 Operation[] operations, 770 Thread mainThread, 771 Barrier startBarrier) { 772 super(lock, id, operations); 773 this.mainThread = mainThread; 774 this.startBarrier = startBarrier; 775 } 776 run()777 public void run() { 778 try { 779 if (DEBUG) { 780 System.out.println("Starting ThreadStress Daemon " + id); 781 } 782 startBarrier.await(); 783 try { 784 int i = 0; 785 while (true) { 786 Operation operation = operations[i]; 787 if (DEBUG) { 788 System.out.println("ThreadStress Daemon " + id 789 + " operation " + i 790 + " is " + operation); 791 } 792 // Ignore the result of the performed operation, making 793 // Exit.perform() essentially a no-op for daemon threads. 794 operation.perform(); 795 i = (i + 1) % operations.length; 796 } 797 } catch (OutOfMemoryError e) { 798 // Catch OutOfMemoryErrors since these can cause the test to fail it they print 799 // the stack trace after "Finishing worker". Note that operations should catch 800 // their own OOME, this guards only agains OOME in the DEBUG output. 801 } 802 if (DEBUG) { 803 System.out.println("Finishing ThreadStress Daemon for " + id); 804 } 805 } catch (Throwable t) { 806 Main.printThrowable(t); 807 // Interrupt the main thread, so that it can orderly shut down 808 // instead of waiting indefinitely for some Barrier. 809 mainThread.interrupt(); 810 } 811 } 812 813 final Thread mainThread; 814 final Barrier startBarrier; 815 } 816 817 // Note: java.util.concurrent.CyclicBarrier.await() allocates memory and may throw OOM. 818 // That is highly undesirable in this test, so we use our own simple barrier class. 819 // The only memory allocation that can happen here is the lock inflation which uses 820 // a native allocation. As such, it should succeed even if the Java heap is full. 821 // If the native allocation surprisingly fails, the program shall abort(). 822 private static class Barrier { Barrier(int initialCount)823 public Barrier(int initialCount) { 824 count = initialCount; 825 } 826 await()827 public synchronized void await() throws InterruptedException { 828 --count; 829 if (count != 0) { 830 do { 831 wait(); 832 } while (count != 0); // Check for spurious wakeup. 833 } else { 834 notifyAll(); 835 } 836 } 837 838 private int count; 839 } 840 841 // Printing a String/Throwable through JNI requires only native memory and space 842 // in the local reference table, so it should succeed even if the Java heap is full. printString(String s)843 private static native void printString(String s); printThrowable(Throwable t)844 private static native void printThrowable(Throwable t); 845 846 static final String finishingWorkerMessage; 847 static final String errnoExceptionName; 848 static { 849 // We pre-allocate the strings in class initializer to avoid const-string 850 // instructions in code using these strings later as they may throw OOME. 851 finishingWorkerMessage = "Finishing worker\n"; 852 errnoExceptionName = "ErrnoException"; 853 } 854 } 855