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 489772 64e46a7cc29ff5e5a3afa19b50150fb4035645ae
parent 489771 abaa52cda0ad84656583a260f33fb64fe569a4ef
child 489773 31ceac3c03fc21c6a53cf667844ffbfe5caad0d9
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersjonco
bugs1497906
milestone64.0a1
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);