summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Vladimir Marko <vmarko@google.com> 2016-06-21 18:21:53 +0100
committer Vladimir Marko <vmarko@google.com> 2016-06-27 12:44:50 +0100
commit3ea7c9e552bc8a87439b6e6db9d143e2716bc62c (patch)
treee19509217d06156f093753e959c41f3327b0c709
parentb94b5706f0b8e2e1c7e1db22274f9f4bae0c4b5a (diff)
Fix 004-ThreadStress.
Synchronize the start of performing the tasks after all threads have been created and runners reported their start. This prevents a daemon thread from allocating all memory before the main thread tries to allocate the next daemon thread, which is the likely culprit of failing to print any output whatsoever (the main thread exits due to an uncaught OOME and printing the message also fails due to OOME). Instead of repeating attempts to print "Finishing worker" until it succeeds, print the message through JNI which does not require any Java heap memory. Similarly, print caught Throwables through JNI. Also make sure to preallocate Strings that are used once we start running tasks to avoid const-string throwing OOME. This also prevents a partial success followed by a retry; we have seen a failure to print the EOL, yielding "Finishing workerFinishing worker". Change the "Thread exited..." message to be printed only in DEBUG mode and compare the full output, ignoring different numbers as the "Starting worker for <id>" may be reordered. Test: "make -j 32 test-art-host-run-test-004-ThreadStress" (4 configs: with or without gcstress, 32-bit and 64-bit) 100 times while also keeping the machine under load with "make -j 32" to repeatedly build another full AOSP tree. Bug: 27371304 Change-Id: I28286d70a08be64c36d0e49dd5558686813ff264
-rwxr-xr-xtest/004-ThreadStress/check4
-rw-r--r--test/004-ThreadStress/expected.txt10
-rw-r--r--test/004-ThreadStress/src/Main.java163
-rw-r--r--test/004-ThreadStress/thread_stress.cc36
-rw-r--r--test/Android.libarttest.mk1
5 files changed, 168 insertions, 46 deletions
diff --git a/test/004-ThreadStress/check b/test/004-ThreadStress/check
index ffbb8cf17e..77e4cdbda0 100755
--- a/test/004-ThreadStress/check
+++ b/test/004-ThreadStress/check
@@ -14,5 +14,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Only compare the last line.
-tail -n 1 "$2" | diff --strip-trailing-cr -q "$1" - >/dev/null \ No newline at end of file
+# Do not compare numbers, so replace numbers with 'N'.
+sed '-es/[0-9][0-9]*/N/g' "$2" | diff --strip-trailing-cr -q "$1" - >/dev/null \ No newline at end of file
diff --git a/test/004-ThreadStress/expected.txt b/test/004-ThreadStress/expected.txt
index a26fb4f191..772faf6e9f 100644
--- a/test/004-ThreadStress/expected.txt
+++ b/test/004-ThreadStress/expected.txt
@@ -1 +1,11 @@
+JNI_OnLoad called
+Starting worker for N
+Starting worker for N
+Starting worker for N
+Starting worker for N
+Starting worker for N
+Finishing worker
+Finishing worker
+Finishing worker
+Finishing worker
Finishing worker
diff --git a/test/004-ThreadStress/src/Main.java b/test/004-ThreadStress/src/Main.java
index acd8e8b344..5cae3983bf 100644
--- a/test/004-ThreadStress/src/Main.java
+++ b/test/004-ThreadStress/src/Main.java
@@ -93,9 +93,7 @@ public class Main implements Runnable {
killTemp = osClass.getDeclaredMethod("kill", int.class, int.class);
} catch (Exception e) {
- if (!e.getClass().getName().equals("ErrnoException")) {
- e.printStackTrace(System.out);
- }
+ Main.printThrowable(e);
}
pid = pidTemp;
@@ -109,8 +107,8 @@ public class Main implements Runnable {
kill.invoke(null, pid, sigquit);
} catch (OutOfMemoryError e) {
} catch (Exception e) {
- if (!e.getClass().getName().equals("ErrnoException")) {
- e.printStackTrace(System.out);
+ if (!e.getClass().getName().equals(Main.errnoExceptionName)) {
+ Main.printThrowable(e);
}
}
return true;
@@ -268,6 +266,7 @@ public class Main implements Runnable {
}
public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
parseAndRun(args);
}
@@ -399,12 +398,21 @@ public class Main implements Runnable {
System.out.println(frequencyMap);
}
- runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
+ try {
+ runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
+ } catch (Throwable t) {
+ // In this case, the output should not contain all the required
+ // "Finishing worker" lines.
+ Main.printThrowable(t);
+ }
}
public static void runTest(final int numberOfThreads, final int numberOfDaemons,
final int operationsPerThread, final Object lock,
Map<Operation, Double> frequencyMap) throws Exception {
+ final Thread mainThread = Thread.currentThread();
+ final Barrier startBarrier = new Barrier(numberOfThreads + numberOfDaemons + 1);
+
// Each normal thread is going to do operationsPerThread
// operations. Each daemon thread will loop over all
// the operations and will not stop.
@@ -438,8 +446,9 @@ public class Main implements Runnable {
}
// Randomize the operation order
Collections.shuffle(Arrays.asList(operations));
- threadStresses[t] = t < numberOfThreads ? new Main(lock, t, operations) :
- new Daemon(lock, t, operations);
+ threadStresses[t] = (t < numberOfThreads)
+ ? new Main(lock, t, operations)
+ : new Daemon(lock, t, operations, mainThread, startBarrier);
}
// Enable to dump operation counts per thread to make sure its
@@ -474,32 +483,41 @@ public class Main implements Runnable {
runners[r] = new Thread("Runner thread " + r) {
final Main threadStress = ts;
public void run() {
- int id = threadStress.id;
- System.out.println("Starting worker for " + id);
- while (threadStress.nextOperation < operationsPerThread) {
- try {
- Thread thread = new Thread(ts, "Worker thread " + id);
- thread.start();
+ try {
+ int id = threadStress.id;
+ // No memory hungry task are running yet, so println() should succeed.
+ System.out.println("Starting worker for " + id);
+ // Wait until all runners and daemons reach the starting point.
+ startBarrier.await();
+ // Run the stress tasks.
+ while (threadStress.nextOperation < operationsPerThread) {
try {
+ Thread thread = new Thread(ts, "Worker thread " + id);
+ thread.start();
thread.join();
- } catch (InterruptedException e) {
- }
- System.out.println("Thread exited for " + id + " with "
- + (operationsPerThread - threadStress.nextOperation)
- + " operations remaining.");
- } catch (OutOfMemoryError e) {
- // Ignore OOME since we need to print "Finishing worker" for the test
- // to pass.
- }
- }
- // Keep trying to print "Finishing worker" until it succeeds.
- while (true) {
- try {
- System.out.println("Finishing worker");
- break;
- } catch (OutOfMemoryError e) {
+ if (DEBUG) {
+ System.out.println(
+ "Thread exited for " + id + " with " +
+ (operationsPerThread - threadStress.nextOperation) +
+ " operations remaining.");
+ }
+ } catch (OutOfMemoryError e) {
+ // Ignore OOME since we need to print "Finishing worker"
+ // for the test to pass. This OOM can come from creating
+ // the Thread or from the DEBUG output.
+ // Note that the Thread creation may fail repeatedly,
+ // preventing the runner from making any progress,
+ // especially if the number of daemons is too high.
+ }
}
+ // Print "Finishing worker" through JNI to avoid OOME.
+ Main.printString(Main.finishingWorkerMessage);
+ } catch (Throwable t) {
+ Main.printThrowable(t);
+ // Interrupt the main thread, so that it can orderly shut down
+ // instead of waiting indefinitely for some Barrier.
+ mainThread.interrupt();
}
}
};
@@ -532,6 +550,9 @@ public class Main implements Runnable {
for (int r = 0; r < runners.length; r++) {
runners[r].start();
}
+ // Wait for all threads to reach the starting point.
+ startBarrier.await();
+ // Wait for runners to finish.
for (int r = 0; r < runners.length; r++) {
runners[r].join();
}
@@ -574,8 +595,14 @@ public class Main implements Runnable {
}
private static class Daemon extends Main {
- private Daemon(Object lock, int id, Operation[] operations) {
+ private Daemon(Object lock,
+ int id,
+ Operation[] operations,
+ Thread mainThread,
+ Barrier startBarrier) {
super(lock, id, operations);
+ this.mainThread = mainThread;
+ this.startBarrier = startBarrier;
}
public void run() {
@@ -583,26 +610,74 @@ public class Main implements Runnable {
if (DEBUG) {
System.out.println("Starting ThreadStress Daemon " + id);
}
- int i = 0;
- while (true) {
- Operation operation = operations[i];
- if (DEBUG) {
- System.out.println("ThreadStress Daemon " + id
- + " operation " + i
- + " is " + operation);
+ startBarrier.await();
+ try {
+ int i = 0;
+ while (true) {
+ Operation operation = operations[i];
+ if (DEBUG) {
+ System.out.println("ThreadStress Daemon " + id
+ + " operation " + i
+ + " is " + operation);
+ }
+ operation.perform();
+ i = (i + 1) % operations.length;
}
- operation.perform();
- i = (i + 1) % operations.length;
+ } catch (OutOfMemoryError e) {
+ // Catch OutOfMemoryErrors since these can cause the test to fail it they print
+ // the stack trace after "Finishing worker". Note that operations should catch
+ // their own OOME, this guards only agains OOME in the DEBUG output.
}
- } catch (OutOfMemoryError e) {
- // Catch OutOfMemoryErrors since these can cause the test to fail it they print
- // the stack trace after "Finishing worker".
- } finally {
if (DEBUG) {
System.out.println("Finishing ThreadStress Daemon for " + id);
}
+ } catch (Throwable t) {
+ Main.printThrowable(t);
+ // Interrupt the main thread, so that it can orderly shut down
+ // instead of waiting indefinitely for some Barrier.
+ mainThread.interrupt();
+ }
+ }
+
+ final Thread mainThread;
+ final Barrier startBarrier;
+ }
+
+ // Note: java.util.concurrent.CyclicBarrier.await() allocates memory and may throw OOM.
+ // That is highly undesirable in this test, so we use our own simple barrier class.
+ // The only memory allocation that can happen here is the lock inflation which uses
+ // a native allocation. As such, it should succeed even if the Java heap is full.
+ // If the native allocation surprisingly fails, the program shall abort().
+ private static class Barrier {
+ public Barrier(int initialCount) {
+ count = initialCount;
+ }
+
+ public synchronized void await() throws InterruptedException {
+ --count;
+ if (count != 0) {
+ do {
+ wait();
+ } while (count != 0); // Check for spurious wakeup.
+ } else {
+ notifyAll();
}
}
+
+ private int count;
}
+ // Printing a String/Throwable through JNI requires only native memory and space
+ // in the local reference table, so it should succeed even if the Java heap is full.
+ private static native void printString(String s);
+ private static native void printThrowable(Throwable t);
+
+ static final String finishingWorkerMessage;
+ static final String errnoExceptionName;
+ static {
+ // We pre-allocate the strings in class initializer to avoid const-string
+ // instructions in code using these strings later as they may throw OOME.
+ finishingWorkerMessage = "Finishing worker\n";
+ errnoExceptionName = "ErrnoException";
+ }
}
diff --git a/test/004-ThreadStress/thread_stress.cc b/test/004-ThreadStress/thread_stress.cc
new file mode 100644
index 0000000000..573c352423
--- /dev/null
+++ b/test/004-ThreadStress/thread_stress.cc
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+
+#include "jni.h"
+#include "mirror/string.h"
+#include "mirror/throwable.h"
+#include "scoped_thread_state_change.h"
+
+namespace art {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_printString(JNIEnv*, jclass, jstring s) {
+ ScopedObjectAccess soa(Thread::Current());
+ std::cout << soa.Decode<mirror::String*>(s)->ToModifiedUtf8();
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_printThrowable(JNIEnv*, jclass, jthrowable t) {
+ ScopedObjectAccess soa(Thread::Current());
+ std::cout << soa.Decode<mirror::Throwable*>(t)->Dump();
+}
+
+} // namespace art
diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk
index 859847449b..01790aea12 100644
--- a/test/Android.libarttest.mk
+++ b/test/Android.libarttest.mk
@@ -25,6 +25,7 @@ LIBARTTEST_COMMON_SRC_FILES := \
004-SignalTest/signaltest.cc \
004-ReferenceMap/stack_walk_refmap_jni.cc \
004-StackWalk/stack_walk_jni.cc \
+ 004-ThreadStress/thread_stress.cc \
004-UnsafeTest/unsafe_test.cc \
044-proxy/native_proxy.cc \
051-thread/thread_test.cc \