Bug 1199203 - Add support for per-thread OOM testing. r=jonco
authorChristian Holler <choller@mozilla.com>
Fri, 11 Sep 2015 16:11:56 +0200
changeset 294636 9c1c2581ad6501c9a8a36920043856d46ec19c20
parent 294635 9394c5f63b56b784dcdb9f70fa0b7f428bdf4d8c
child 294637 caec7964e8f756acd30cf7ffe5999106cde6b350
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1199203
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1199203 - Add support for per-thread OOM testing. r=jonco
js/public/Utility.h
js/src/builtin/TestingFunctions.cpp
js/src/jsapi.cpp
js/src/jsutil.cpp
js/src/vm/HelperThreads.cpp
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -57,16 +57,56 @@ extern MOZ_NORETURN MOZ_COLD JS_PUBLIC_A
 JS_Assert(const char* s, const char* file, int ln);
 
 /*
  * Custom allocator support for SpiderMonkey
  */
 #if defined JS_USE_CUSTOM_ALLOCATOR
 # include "jscustomallocator.h"
 #else
+
+namespace js {
+namespace oom {
+
+/*
+ * To make testing OOM in certain helper threads more effective,
+ * allow restricting the OOM testing to a certain helper thread
+ * type. This allows us to fail e.g. in off-thread script parsing
+ * without causing an OOM in the main thread first.
+ */
+enum ThreadType {
+    THREAD_TYPE_NONE,           // 0
+    THREAD_TYPE_MAIN,           // 1
+    THREAD_TYPE_ASMJS,          // 2
+    THREAD_TYPE_ION,            // 3
+    THREAD_TYPE_PARSE,          // 4
+    THREAD_TYPE_COMPRESS,       // 5
+    THREAD_TYPE_GCHELPER,       // 6
+    THREAD_TYPE_GCPARALLEL,     // 7
+    THREAD_TYPE_MAX             // Used to check shell function arguments
+};
+
+
+/*
+ * Getter/Setter functions to encapsulate mozilla::ThreadLocal,
+ * implementation is in jsutil.cpp.
+ */
+# if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+extern bool InitThreadType(void);
+extern void SetThreadType(ThreadType);
+extern uint32_t GetThreadType(void);
+# else
+inline bool InitThreadType(void) { return true; }
+inline void SetThreadType(ThreadType t) {};
+inline uint32_t GetThreadType(void) { return 0; }
+# endif
+
+} /* namespace oom */
+} /* namespace js */
+
 # if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
 /*
  * In order to test OOM conditions, when the testing function
  * oomAfterAllocations COUNT is passed, we fail continuously after the NUM'th
  * allocation from now.
  */
 extern JS_PUBLIC_DATA(uint32_t) OOM_maxAllocations; /* set in builtin/TestingFunctions.cpp */
@@ -78,26 +118,38 @@ static MOZ_NEVER_INLINE void js_failedAl
 #define JS_OOM_CALL_BP_FUNC() js_failedAllocBreakpoint()
 #else
 #define JS_OOM_CALL_BP_FUNC() do {} while(0)
 #endif
 
 namespace js {
 namespace oom {
 
+extern JS_PUBLIC_DATA(uint32_t) targetThread;
+
+static inline bool
+OOMThreadCheck()
+{
+    return (!js::oom::targetThread 
+            || js::oom::targetThread == js::oom::GetThreadType());
+}
+
 static inline bool
 IsSimulatedOOMAllocation()
 {
-    return OOM_counter == OOM_maxAllocations ||
-           (OOM_counter > OOM_maxAllocations && OOM_failAlways);
+    return OOMThreadCheck() && (OOM_counter == OOM_maxAllocations ||
+           (OOM_counter > OOM_maxAllocations && OOM_failAlways));
 }
 
 static inline bool
 ShouldFailWithOOM()
 {
+    if (!OOMThreadCheck())
+        return false;
+
     OOM_counter++;
     if (IsSimulatedOOMAllocation()) {
         JS_OOM_CALL_BP_FUNC();
         return true;
     }
     return false;
 }
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -982,43 +982,73 @@ DisableTrackAllocations(JSContext* cx, u
     return true;
 }
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 static bool
 OOMAfterAllocations(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (args.length() != 1) {
+    if (args.length() < 1) {
         JS_ReportError(cx, "count argument required");
         return false;
     }
 
+    if (args.length() > 2) {
+        JS_ReportError(cx, "too many arguments");
+        return false;
+    }
+
+    uint32_t targetThread = 0;
+    if (!ToUint32(cx, args.get(1), &targetThread))
+        return false;
+
+    if (targetThread >= js::oom::THREAD_TYPE_MAX) {
+        JS_ReportError(cx, "invalid thread type specified");
+        return false;
+    }
+
     uint32_t count;
-    if (!JS::ToUint32(cx, args[0], &count))
+    if (!JS::ToUint32(cx, args.get(0), &count))
         return false;
 
+    js::oom::targetThread = targetThread;
     OOM_maxAllocations = OOM_counter + count;
     OOM_failAlways = true;
     return true;
 }
 
 static bool
 OOMAtAllocation(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (args.length() != 1) {
+    if (args.length() < 1) {
         JS_ReportError(cx, "count argument required");
         return false;
     }
 
+    if (args.length() > 2) {
+        JS_ReportError(cx, "too many arguments");
+        return false;
+    }
+
+    uint32_t targetThread = 0;
+    if (!ToUint32(cx, args.get(1), &targetThread))
+        return false;
+
+    if (targetThread >= js::oom::THREAD_TYPE_MAX) {
+        JS_ReportError(cx, "invalid thread type specified");
+        return false;
+    }
+
     uint32_t count;
-    if (!JS::ToUint32(cx, args[0], &count))
+    if (!JS::ToUint32(cx, args.get(0), &count))
         return false;
 
+    js::oom::targetThread = targetThread;
     OOM_maxAllocations = OOM_counter + count;
     OOM_failAlways = false;
     return true;
 }
 
 static bool
 ResetOOMFailure(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -2827,25 +2857,27 @@ static const JSFunctionSpecWithHelp Test
 "  object metadata callback that will override any other object metadata\n"
 "  callback that may be set."),
 
     JS_FN_HELP("disableTrackAllocations", DisableTrackAllocations, 0, 0,
 "disableTrackAllocations()",
 "  Stop capturing the JS stack at every allocation."),
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
-    JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 1, 0,
-"oomAfterAllocations(count)",
+    JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 2, 0,
+"oomAfterAllocations(count [,threadType])",
 "  After 'count' js_malloc memory allocations, fail every following allocation\n"
-"  (return NULL)."),
-
-    JS_FN_HELP("oomAtAllocation", OOMAtAllocation, 1, 0,
-"oomAtAllocation(count)",
+"  (return nullptr). The optional thread type limits the effect to the\n"
+"  specified type of helper thread."),
+
+    JS_FN_HELP("oomAtAllocation", OOMAtAllocation, 2, 0,
+"oomAtAllocation(count [,threadType])",
 "  After 'count' js_malloc memory allocations, fail the next allocation\n"
-"  (return NULL)."),
+"  (return nullptr). The optional thread type limits the effect to the\n"
+"  specified type of helper thread."),
 
     JS_FN_HELP("resetOOMFailure", ResetOOMFailure, 0, 0,
 "resetOOMFailure()",
 "  Remove the allocation failure scheduled by either oomAfterAllocations() or\n"
 "  oomAtAllocation() and return whether any allocation had been caused to fail."),
 #endif
 
     JS_FN_HELP("makeFakePromise", MakeFakePromise, 0, 0,
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -585,16 +585,22 @@ JS_Init(void)
 #ifdef DEBUG
     CheckMessageParameterCounts();
 #endif
 
     using js::TlsPerThreadData;
     if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init())
         return false;
 
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+    if (!js::oom::InitThreadType())
+        return false;
+    js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN);
+#endif
+
     jit::ExecutableAllocator::initStatic();
 
     if (!jit::InitializeIon())
         return false;
 
 #if EXPOSE_INTL_API
     UErrorCode err = U_ZERO_ERROR;
     u_init(&err);
--- a/js/src/jsutil.cpp
+++ b/js/src/jsutil.cpp
@@ -6,16 +6,17 @@
 
 /* Various JS utility functions. */
 
 #include "jsutil.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/PodOperations.h"
+#include "mozilla/ThreadLocal.h"
 
 #include <stdio.h>
 
 #include "jstypes.h"
 
 #ifdef WIN32
 #    include "jswin.h"
 #endif
@@ -27,17 +28,40 @@ using namespace js;
 using mozilla::CeilingLog2Size;
 using mozilla::PodArrayZero;
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 /* For JS_OOM_POSSIBLY_FAIL in jsutil.h. */
 JS_PUBLIC_DATA(uint32_t) OOM_maxAllocations = UINT32_MAX;
 JS_PUBLIC_DATA(uint32_t) OOM_counter = 0;
 JS_PUBLIC_DATA(bool) OOM_failAlways = true;
-#endif
+namespace js {
+namespace oom {
+
+JS_PUBLIC_DATA(uint32_t) targetThread = 0;
+JS_PUBLIC_DATA(mozilla::ThreadLocal<uint32_t>) threadType;
+
+bool
+InitThreadType(void) {
+    return threadType.initialized() || threadType.init();
+}
+
+void
+SetThreadType(ThreadType type) {
+    threadType.set(type);
+}
+
+uint32_t
+GetThreadType(void) {
+    return threadType.get();
+}
+
+} // namespace oom
+} // namespace js
+#endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
 JS_PUBLIC_API(void)
 JS_Assert(const char* s, const char* file, int ln)
 {
     MOZ_ReportAssertionFailure(s, file, ln);
     MOZ_CRASH();
 }
 
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1446,24 +1446,31 @@ HelperThread::threadLoop()
                 HelperThreadState().canStartGCParallelTask())
             {
                 break;
             }
             HelperThreadState().wait(GlobalHelperThreadState::PRODUCER);
         }
 
         // Dispatch tasks, prioritizing AsmJS work.
-        if (HelperThreadState().canStartAsmJSCompile())
+        if (HelperThreadState().canStartAsmJSCompile()) {
+            js::oom::SetThreadType(js::oom::THREAD_TYPE_ASMJS);
             handleAsmJSWorkload();
-        else if (ionCompile)
+        } else if (ionCompile) {
+            js::oom::SetThreadType(js::oom::THREAD_TYPE_ION);
             handleIonWorkload();
-        else if (HelperThreadState().canStartParseTask())
+        } else if (HelperThreadState().canStartParseTask()) {
+            js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);
             handleParseWorkload();
-        else if (HelperThreadState().canStartCompressionTask())
+        } else if (HelperThreadState().canStartCompressionTask()) {
+            js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS);
             handleCompressionWorkload();
-        else if (HelperThreadState().canStartGCHelperTask())
+        } else if (HelperThreadState().canStartGCHelperTask()) {
+            js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER);
             handleGCHelperWorkload();
-        else if (HelperThreadState().canStartGCParallelTask())
+        } else if (HelperThreadState().canStartGCParallelTask()) {
+            js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL);
             handleGCParallelWorkload();
-        else
+        } else {
             MOZ_CRASH("No task to perform");
+        }
     }
 }