Bug 1497906 - Add THREAD_TYPE_CURRENT to simulate errors on a single worker thread. r=jonco
authorNicolas B. Pierron <nicolas.b.pierron@nbp.name>
Mon, 15 Oct 2018 16:41:33 +0200
changeset 499959 64e46a7cc29ff5e5a3afa19b50150fb4035645ae
parent 499958 abaa52cda0ad84656583a260f33fb64fe569a4ef
child 499960 31ceac3c03fc21c6a53cf667844ffbfe5caad0d9
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1497906
milestone64.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 1497906 - Add THREAD_TYPE_CURRENT to simulate errors on a single worker thread. r=jonco
js/public/Utility.h
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/self-test/oom-test-bug1497906.js
js/src/jsutil.cpp
js/src/shell/js.cpp
js/src/tests/shell/mailbox.js
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -67,16 +67,17 @@ enum ThreadType {
     THREAD_TYPE_PARSE,          // 4
     THREAD_TYPE_COMPRESS,       // 5
     THREAD_TYPE_GCHELPER,       // 6
     THREAD_TYPE_GCPARALLEL,     // 7
     THREAD_TYPE_PROMISE_TASK,   // 8
     THREAD_TYPE_ION_FREE,       // 9
     THREAD_TYPE_WASM_TIER2,     // 10
     THREAD_TYPE_WORKER,         // 11
+    THREAD_TYPE_CURRENT,        // 12 -- Special code to only track the current thread.
     THREAD_TYPE_MAX             // Used to check shell function arguments
 };
 
 namespace oom {
 
 /*
  * Theads are tagged only in certain debug contexts.  Notably, to make testing
  * OOM in certain helper threads more effective, we allow restricting the OOM
@@ -87,26 +88,34 @@ namespace oom {
  * is in jsutil.cpp.
  */
 # if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
 // Define the range of threads tested by simulated OOM testing and the
 // like. Testing worker threads is not supported.
 const ThreadType FirstThreadTypeToTest = THREAD_TYPE_MAIN;
 const ThreadType LastThreadTypeToTest = THREAD_TYPE_WASM_TIER2;
+const ThreadType WorkerFirstThreadTypeToTest = THREAD_TYPE_CURRENT;
+const ThreadType WorkerLastThreadTypeToTest = THREAD_TYPE_CURRENT;
 
 extern bool InitThreadType(void);
 extern void SetThreadType(ThreadType);
 extern JS_FRIEND_API(uint32_t) GetThreadType(void);
+extern JS_FRIEND_API(uint32_t) GetAllocationThreadType(void);
+extern JS_FRIEND_API(uint32_t) GetStackCheckThreadType(void);
+extern JS_FRIEND_API(uint32_t) GetInterruptCheckThreadType(void);
 
 # else
 
 inline bool InitThreadType(void) { return true; }
 inline void SetThreadType(ThreadType t) {};
 inline uint32_t GetThreadType(void) { return 0; }
+inline uint32_t GetAllocationThreadType(void) { return 0; }
+inline uint32_t GetStackCheckThreadType(void) { return 0; }
+inline uint32_t GetInterruptCheckThreadType(void) { return 0; }
 
 # endif
 
 } /* namespace oom */
 } /* namespace js */
 
 # if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
@@ -138,17 +147,18 @@ extern void
 SimulateOOMAfter(uint64_t allocations, uint32_t thread, bool always);
 
 extern void
 ResetSimulatedOOM();
 
 inline bool
 IsThreadSimulatingOOM()
 {
-    return js::oom::targetThread && js::oom::targetThread == js::oom::GetThreadType();
+    return js::oom::targetThread &&
+        js::oom::targetThread == js::oom::GetAllocationThreadType();
 }
 
 inline bool
 IsSimulatedOOMAllocation()
 {
     return IsThreadSimulatingOOM() &&
            (counter == maxAllocations || (counter > maxAllocations && failAlways));
 }
@@ -186,17 +196,18 @@ extern void
 SimulateStackOOMAfter(uint64_t checks, uint32_t thread, bool always);
 
 extern void
 ResetSimulatedStackOOM();
 
 inline bool
 IsThreadSimulatingStackOOM()
 {
-    return js::oom::stackTargetThread && js::oom::stackTargetThread == js::oom::GetThreadType();
+    return js::oom::stackTargetThread &&
+        js::oom::stackTargetThread == js::oom::GetStackCheckThreadType();
 }
 
 inline bool
 IsSimulatedStackOOMCheck()
 {
     return IsThreadSimulatingStackOOM() &&
            (stackCheckCounter == maxStackChecks || (stackCheckCounter > maxStackChecks && stackCheckFailAlways));
 }
@@ -235,17 +246,18 @@ extern void
 SimulateInterruptAfter(uint64_t checks, uint32_t thread, bool always);
 
 extern void
 ResetSimulatedInterrupt();
 
 inline bool
 IsThreadSimulatingInterrupt()
 {
-    return js::oom::interruptTargetThread && js::oom::interruptTargetThread == js::oom::GetThreadType();
+    return js::oom::interruptTargetThread &&
+        js::oom::interruptTargetThread == js::oom::GetInterruptCheckThreadType();
 }
 
 inline bool
 IsSimulatedInterruptCheck()
 {
     return IsThreadSimulatingInterrupt() &&
            (interruptCheckCounter == maxInterruptChecks || (interruptCheckCounter > maxInterruptChecks && interruptCheckFailAlways));
 }
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1889,17 +1889,17 @@ RunIterativeFailureTest(JSContext* cx, c
 #endif
 
     size_t compartmentCount = CountCompartments(cx);
 
     RootedValue exception(cx);
 
     simulator.setup(cx);
 
-    for (unsigned thread = params.threadStart; thread < params.threadEnd; thread++) {
+    for (unsigned thread = params.threadStart; thread <= params.threadEnd; thread++) {
         if (params.verbose) {
             fprintf(stderr, "thread %d\n", thread);
         }
 
         unsigned iteration = 1;
         bool failureWasSimulated;
         do {
             if (params.verbose) {
@@ -2030,30 +2030,39 @@ ParseIterativeFailureTestParams(JSContex
     }
 
     // There are some places where we do fail without raising an exception, so
     // we can't expose this to the fuzzers by default.
     if (fuzzingSafe) {
         params->expectExceptionOnFailure = false;
     }
 
-    // Test all threads by default.
-    params->threadStart = oom::FirstThreadTypeToTest;
-    params->threadEnd = oom::LastThreadTypeToTest;
+    // Test all threads by default except worker threads, except if we are
+    // running in a worker thread in which case only the worker thread which
+    // requested the simulation is tested.
+    if (js::oom::GetThreadType() == js::THREAD_TYPE_WORKER) {
+        params->threadStart = oom::WorkerFirstThreadTypeToTest;
+        params->threadEnd = oom::WorkerLastThreadTypeToTest;
+    } else {
+        params->threadStart = oom::FirstThreadTypeToTest;
+        params->threadEnd = oom::LastThreadTypeToTest;
+    }
 
     // Test a single thread type if specified by the OOM_THREAD environment variable.
     int threadOption = 0;
     if (EnvVarAsInt("OOM_THREAD", &threadOption)) {
-        if (threadOption < oom::FirstThreadTypeToTest || threadOption > oom::LastThreadTypeToTest) {
+        if (threadOption < oom::FirstThreadTypeToTest || threadOption > oom::LastThreadTypeToTest ||
+            threadOption != js::THREAD_TYPE_CURRENT)
+        {
             JS_ReportErrorASCII(cx, "OOM_THREAD value out of range.");
             return false;
         }
 
         params->threadStart = threadOption;
-        params->threadEnd = threadOption + 1;
+        params->threadEnd = threadOption;
     }
 
     params->verbose = EnvVarIsDefined("OOM_VERBOSE");
 
     return true;
 }
 
 struct OOMSimulator : public IterativeFailureSimulator
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/self-test/oom-test-bug1497906.js
@@ -0,0 +1,27 @@
+// |jit-test| skip-if: !('oomTest' in this && 'stackTest' in this) || helperThreadCount() === 0
+
+// Check the we can run a stack test on all threads except worker threads while
+// a worker thread is running a OOM test at the same time.
+//
+// This test case uses setSharedObject to "synchronize" between a worker thread
+// and the main thread. This worker thread enters the oomTest section, while the
+// main thread waits. Then the worker thread remains in the oomTest section
+// until the main thread finish entering and existing its stackTest section.
+
+setSharedObject(0);
+evalInWorker(`
+    oomTest(() => {
+        if (getSharedObject() < 2) {
+            setSharedObject(1);
+            while (getSharedObject() != 2) {
+            }
+        }
+    });
+`);
+
+while (getSharedObject() != 1) {
+    // poor-man wait condition.
+}
+
+stackTest(() => 42);
+setSharedObject(2);
--- a/js/src/jsutil.cpp
+++ b/js/src/jsutil.cpp
@@ -33,134 +33,178 @@ mozilla::Atomic<AutoEnterOOMUnsafeRegion
 
 namespace oom {
 
 JS_PUBLIC_DATA(uint32_t) targetThread = 0;
 MOZ_THREAD_LOCAL(uint32_t) threadType;
 JS_PUBLIC_DATA(uint64_t) maxAllocations = UINT64_MAX;
 JS_PUBLIC_DATA(uint64_t) counter = 0;
 JS_PUBLIC_DATA(bool) failAlways = true;
+MOZ_THREAD_LOCAL(bool) isAllocationThread;
 
 JS_PUBLIC_DATA(uint32_t) stackTargetThread = 0;
 JS_PUBLIC_DATA(uint64_t) maxStackChecks = UINT64_MAX;
 JS_PUBLIC_DATA(uint64_t) stackCheckCounter = 0;
 JS_PUBLIC_DATA(bool) stackCheckFailAlways = true;
+MOZ_THREAD_LOCAL(bool) isStackCheckThread;
 
 JS_PUBLIC_DATA(uint32_t) interruptTargetThread = 0;
 JS_PUBLIC_DATA(uint64_t) maxInterruptChecks = UINT64_MAX;
 JS_PUBLIC_DATA(uint64_t) interruptCheckCounter = 0;
 JS_PUBLIC_DATA(bool) interruptCheckFailAlways = true;
+MOZ_THREAD_LOCAL(bool) isInterruptCheckThread;
 
 bool
 InitThreadType(void) {
-    return threadType.init();
+    return threadType.init() && isAllocationThread.init() &&
+        isStackCheckThread.init() && isInterruptCheckThread.init();
 }
 
 void
 SetThreadType(ThreadType type) {
     threadType.set(type);
+    isAllocationThread.set(false);
+    isStackCheckThread.set(false);
+    isInterruptCheckThread.set(false);
 }
 
 uint32_t
 GetThreadType(void) {
     return threadType.get();
 }
 
+uint32_t
+GetAllocationThreadType(void) {
+    if (isAllocationThread.get()) {
+        return js::THREAD_TYPE_CURRENT;
+    }
+    return threadType.get();
+}
+
+uint32_t
+GetStackCheckThreadType(void) {
+    if (isStackCheckThread.get()) {
+        return js::THREAD_TYPE_CURRENT;
+    }
+    return threadType.get();
+}
+
+uint32_t
+GetInterruptCheckThreadType(void) {
+    if (isInterruptCheckThread.get()) {
+        return js::THREAD_TYPE_CURRENT;
+    }
+    return threadType.get();
+}
+
 static inline bool
 IsHelperThreadType(uint32_t thread)
 {
-    return thread != THREAD_TYPE_NONE && thread != THREAD_TYPE_MAIN;
+    return thread != THREAD_TYPE_NONE && thread != THREAD_TYPE_MAIN &&
+        thread != THREAD_TYPE_CURRENT;
 }
 
 void
 SimulateOOMAfter(uint64_t allocations, uint32_t thread, bool always)
 {
     Maybe<AutoLockHelperThreadState> lock;
     if (IsHelperThreadType(targetThread) || IsHelperThreadType(thread)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     MOZ_ASSERT(counter + allocations > counter);
     MOZ_ASSERT(thread > js::THREAD_TYPE_NONE && thread < js::THREAD_TYPE_MAX);
     targetThread = thread;
+    if (thread == js::THREAD_TYPE_CURRENT) {
+        isAllocationThread.set(true);
+    }
     maxAllocations = counter + allocations;
     failAlways = always;
 }
 
 void
 ResetSimulatedOOM()
 {
     Maybe<AutoLockHelperThreadState> lock;
     if (IsHelperThreadType(targetThread)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     targetThread = THREAD_TYPE_NONE;
+    isAllocationThread.set(false);
     maxAllocations = UINT64_MAX;
     failAlways = false;
 }
 
 void
 SimulateStackOOMAfter(uint64_t checks, uint32_t thread, bool always)
 {
     Maybe<AutoLockHelperThreadState> lock;
     if (IsHelperThreadType(stackTargetThread) || IsHelperThreadType(thread)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     MOZ_ASSERT(stackCheckCounter + checks > stackCheckCounter);
     MOZ_ASSERT(thread > js::THREAD_TYPE_NONE && thread < js::THREAD_TYPE_MAX);
     stackTargetThread = thread;
+    if (thread == js::THREAD_TYPE_CURRENT) {
+        isStackCheckThread.set(true);
+    }
     maxStackChecks = stackCheckCounter + checks;
     stackCheckFailAlways = always;
 }
 
 void
 ResetSimulatedStackOOM()
 {
     Maybe<AutoLockHelperThreadState> lock;
     if (IsHelperThreadType(stackTargetThread)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     stackTargetThread = THREAD_TYPE_NONE;
+    isStackCheckThread.set(false);
     maxStackChecks = UINT64_MAX;
     stackCheckFailAlways = false;
 }
 
 void
 SimulateInterruptAfter(uint64_t checks, uint32_t thread, bool always)
 {
     Maybe<AutoLockHelperThreadState> lock;
     if (IsHelperThreadType(interruptTargetThread) || IsHelperThreadType(thread)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     MOZ_ASSERT(interruptCheckCounter + checks > interruptCheckCounter);
     MOZ_ASSERT(thread > js::THREAD_TYPE_NONE && thread < js::THREAD_TYPE_MAX);
     interruptTargetThread = thread;
+    if (thread == js::THREAD_TYPE_CURRENT) {
+        isInterruptCheckThread.set(true);
+    }
     maxInterruptChecks = interruptCheckCounter + checks;
     interruptCheckFailAlways = always;
 }
 
 void
 ResetSimulatedInterrupt()
 {
     Maybe<AutoLockHelperThreadState> lock;
     if (IsHelperThreadType(interruptTargetThread)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
     interruptTargetThread = THREAD_TYPE_NONE;
+    isInterruptCheckThread.set(false);
     maxInterruptChecks = UINT64_MAX;
     interruptCheckFailAlways = false;
 }
 
 } // namespace oom
 } // namespace js
 #endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -6396,27 +6396,29 @@ DisableGeckoProfiling(JSContext* cx, uns
 // objects on each read.  The alternatives are to cache created objects locally,
 // but this retains storage we don't need to retain, or to somehow clear the
 // mailbox locally, but this creates a coordination headache.  Buyer beware.
 
 enum class MailboxTag {
     Empty,
     SharedArrayBuffer,
     WasmMemory,
-    WasmModule
+    WasmModule,
+    Number,
 };
 
 struct SharedObjectMailbox
 {
     union Value {
         struct {
             SharedArrayRawBuffer* buffer;
             uint32_t              length;
         } sarb;
         const wasm::Module*       module;
+        double                    number;
     };
 
     SharedObjectMailbox() : tag(MailboxTag::Empty) {}
 
     MailboxTag tag;
     Value      val;
 };
 
@@ -6436,16 +6438,17 @@ static void
 DestructSharedObjectMailbox()
 {
     // All workers need to have terminated at this point.
 
     {
         auto mbx = sharedObjectMailbox->lock();
         switch (mbx->tag) {
           case MailboxTag::Empty:
+          case MailboxTag::Number:
             break;
           case MailboxTag::SharedArrayBuffer:
           case MailboxTag::WasmMemory:
             mbx->val.sarb.buffer->dropReference();
             break;
           case MailboxTag::WasmModule:
             mbx->val.module->Release();
             break;
@@ -6465,16 +6468,20 @@ GetSharedObject(JSContext* cx, unsigned 
     RootedObject newObj(cx);
 
     {
         auto mbx = sharedObjectMailbox->lock();
         switch (mbx->tag) {
           case MailboxTag::Empty: {
             break;
           }
+          case MailboxTag::Number: {
+            args.rval().setNumber(mbx->val.number);
+            return true;
+          }
           case MailboxTag::SharedArrayBuffer:
           case MailboxTag::WasmMemory: {
             // Flag was set in the sender; ensure it is set in the receiver.
             MOZ_ASSERT(cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
 
             // The protocol for creating a SAB requires the refcount to be
             // incremented prior to the SAB creation.
 
@@ -6584,28 +6591,33 @@ SetSharedObject(JSContext* cx, unsigned 
         } else if (obj->is<WasmModuleObject>()) {
             tag = MailboxTag::WasmModule;
             value.module = &obj->as<WasmModuleObject>().module();
             value.module->AddRef();
         } else {
             JS_ReportErrorASCII(cx, "Invalid argument to SetSharedObject");
             return false;
         }
+    } else if (args.get(0).isNumber()) {
+        tag = MailboxTag::Number;
+        value.number = args.get(0).toNumber();
+        // Nothing
     } else if (args.get(0).isNullOrUndefined()) {
         // Nothing
     } else {
         JS_ReportErrorASCII(cx, "Invalid argument to SetSharedObject");
         return false;
     }
 
     {
         auto mbx = sharedObjectMailbox->lock();
 
         switch (mbx->tag) {
           case MailboxTag::Empty:
+          case MailboxTag::Number:
             break;
           case MailboxTag::SharedArrayBuffer:
           case MailboxTag::WasmMemory:
             mbx->val.sarb.buffer->dropReference();
             break;
           case MailboxTag::WasmModule:
             mbx->val.module->Release();
             break;
--- a/js/src/tests/shell/mailbox.js
+++ b/js/src/tests/shell/mailbox.js
@@ -28,16 +28,19 @@ assertEq(getSharedObject() == null, fals
 
 var v = getSharedObject();
 assertEq(v.byteLength, mem.buffer.byteLength); // Looks like what we put in?
 var w = new Int32Array(v);
 mem[0] = 314159;
 assertEq(w[0], 314159);		// Shares memory (locally) with what we put in?
 mem[0] = 0;
 
+setSharedObject(3.14);	// Share numbers
+assertEq(getSharedObject(), 3.14);
+
 setSharedObject(null);	// Setting to null clears to null
 assertEq(getSharedObject(), null);
 
 setSharedObject(mem.buffer);
 setSharedObject(undefined); // Setting to undefined clears to null
 assertEq(getSharedObject(), null);
 
 setSharedObject(mem.buffer);
@@ -46,17 +49,16 @@ assertEq(getSharedObject(), null);
 
 // Non-shared objects cannot be stored in the mbx
 
 assertThrowsInstanceOf(() => setSharedObject({x:10, y:20}), Error);
 assertThrowsInstanceOf(() => setSharedObject([1,2]), Error);
 assertThrowsInstanceOf(() => setSharedObject(new ArrayBuffer(10)), Error);
 assertThrowsInstanceOf(() => setSharedObject(new Int32Array(10)), Error);
 assertThrowsInstanceOf(() => setSharedObject(false), Error);
-assertThrowsInstanceOf(() => setSharedObject(3.14), Error);
 assertThrowsInstanceOf(() => setSharedObject(mem), Error);
 assertThrowsInstanceOf(() => setSharedObject("abracadabra"), Error);
 assertThrowsInstanceOf(() => setSharedObject(() => 37), Error);
 
 // We can store wasm shared memories, too
 
 if (!this.WebAssembly || !wasmThreadsSupported()) {
     reportCompare(true, true);