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