Bug 1068684 - synchronous gc when the number of SharedArrayBuffers gets large. r=luke, r=jonco
authorLars T Hansen <lhansen@mozilla.com>
Fri, 24 Oct 2014 18:37:31 +0200
changeset 212278 9756bf697be4564d0113b36621b91cd4149ac7de
parent 212276 6ad2da5715e8bc29d01f0a17aa7ba2a056f38b00
child 212279 bcad05880c39de440d6765840500ada694ffba39
push id27704
push userkwierso@gmail.com
push dateSat, 25 Oct 2014 01:25:30 +0000
treeherdermozilla-central@e37231060eb4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke, jonco
bugs1068684
milestone36.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 1068684 - synchronous gc when the number of SharedArrayBuffers gets large. r=luke, r=jonco
js/public/GCAPI.h
js/src/shell/js.cpp
js/src/vm/SharedArrayObject.cpp
js/src/vm/SharedArrayObject.h
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -43,16 +43,17 @@ namespace JS {
     D(TOO_MUCH_MALLOC)                          \
     D(ALLOC_TRIGGER)                            \
     D(DEBUG_GC)                                 \
     D(COMPARTMENT_REVIVED)                      \
     D(RESET)                                    \
     D(OUT_OF_NURSERY)                           \
     D(EVICT_NURSERY)                            \
     D(FULL_STORE_BUFFER)                        \
+    D(SHARED_MEMORY_LIMIT)                      \
                                                 \
     /* These are reserved for future use. */    \
     D(RESERVED0)                                \
     D(RESERVED1)                                \
     D(RESERVED2)                                \
     D(RESERVED3)                                \
     D(RESERVED4)                                \
     D(RESERVED5)                                \
@@ -64,17 +65,16 @@ namespace JS {
     D(RESERVED11)                               \
     D(RESERVED12)                               \
     D(RESERVED13)                               \
     D(RESERVED14)                               \
     D(RESERVED15)                               \
     D(RESERVED16)                               \
     D(RESERVED17)                               \
     D(RESERVED18)                               \
-    D(RESERVED19)                               \
                                                 \
     /* Reasons from Firefox */                  \
     D(DOM_WINDOW_UTILS)                         \
     D(COMPONENT_UTILS)                          \
     D(MEM_PRESSURE)                             \
     D(CC_WAITING)                               \
     D(CC_FORCED)                                \
     D(LOAD_END)                                 \
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -986,16 +986,33 @@ class AutoNewContext
             newRequest.reset();
             if (throwing)
                 JS_SetPendingException(oldcx, exc);
             DestroyContext(newcx, false);
         }
     }
 };
 
+static void
+my_LargeAllocFailCallback(void *data)
+{
+    JSContext *cx = (JSContext*)data;
+    JSRuntime *rt = cx->runtime();
+
+    if (InParallelSection() || !cx->allowGC())
+        return;
+
+    MOZ_ASSERT(!rt->isHeapBusy());
+    MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess());
+
+    JS::PrepareForFullGC(rt);
+    AutoKeepAtoms keepAtoms(cx->perThreadData);
+    rt->gc.gc(GC_NORMAL, JS::gcreason::SHARED_MEMORY_LIMIT);
+}
+
 static const uint32_t CacheEntry_SOURCE = 0;
 static const uint32_t CacheEntry_BYTECODE = 1;
 
 static const JSClass CacheEntry_class = {
     "CacheEntryObject", JSCLASS_HAS_RESERVED_SLOTS(2),
     JS_PropertyStub,       /* addProperty */
     JS_DeletePropertyStub, /* delProperty */
     JS_PropertyStub,       /* getProperty */
@@ -2711,16 +2728,18 @@ WorkerMain(void *arg)
 
     JSContext *cx = NewContext(rt);
     if (!cx) {
         JS_DestroyRuntime(rt);
         js_delete(input);
         return;
     }
 
+    JS::SetLargeAllocationFailureCallback(rt, my_LargeAllocFailCallback, (void*)cx);
+
     do {
         JSAutoRequest ar(cx);
 
         JS::CompartmentOptions compartmentOptions;
         compartmentOptions.setVersion(JSVERSION_LATEST);
         RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr));
         if (!global)
             break;
@@ -2733,16 +2752,18 @@ WorkerMain(void *arg)
 
         RootedScript script(cx);
         if (!JS::Compile(cx, global, options, input->chars, input->length, &script))
             break;
         RootedValue result(cx);
         JS_ExecuteScript(cx, global, script, &result);
     } while (0);
 
+    JS::SetLargeAllocationFailureCallback(rt, nullptr, nullptr);
+
     DestroyContext(cx, false);
     JS_DestroyRuntime(rt);
 
     js_delete(input);
 }
 
 Vector<PRThread *, 0, SystemAllocPolicy> workerThreads;
 
@@ -6128,16 +6149,18 @@ main(int argc, char **argv, char **envp)
 
     cx = NewContext(rt);
     if (!cx)
         return 1;
 
     JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL);
     JS_SetGCParameterForThread(cx, JSGC_MAX_CODE_CACHE_BYTES, 16 * 1024 * 1024);
 
+    JS::SetLargeAllocationFailureCallback(rt, my_LargeAllocFailCallback, (void*)cx);
+
     // Set some parameters to allow incremental GC in low memory conditions,
     // as is done for the browser, except in more-deterministic builds or when
     // disabled by command line options.
 #ifndef JS_MORE_DETERMINISTIC
     if (!op.getBoolOption("no-incremental-gc")) {
         JS_SetGCParameter(rt, JSGC_DYNAMIC_HEAP_GROWTH, 1);
         JS_SetGCParameter(rt, JSGC_DYNAMIC_MARK_SLICE, 1);
         JS_SetGCParameter(rt, JSGC_SLICE_TIME_BUDGET, 10);
@@ -6148,16 +6171,18 @@ main(int argc, char **argv, char **envp)
 
     result = Shell(cx, &op, envp);
 
 #ifdef DEBUG
     if (OOM_printAllocationCount)
         printf("OOM max count: %u\n", OOM_counter);
 #endif
 
+    JS::SetLargeAllocationFailureCallback(rt, nullptr, nullptr);
+
     DestroyContext(cx, true);
 
     KillWatchdog();
 
     gInterruptFunc.reset();
 
     MOZ_ASSERT_IF(!CanUseExtraThreads(), workerThreads.empty());
     for (size_t i = 0; i < workerThreads.length(); i++)
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -69,37 +69,61 @@ MarkValidRegion(void *addr, size_t len)
 
 #ifdef JS_CODEGEN_X64
 // Since this SharedArrayBuffer will likely be used for asm.js code, prepare it
 // for asm.js by mapping the 4gb protected zone described in AsmJSValidate.h.
 // Since we want to put the SharedArrayBuffer header immediately before the
 // heap but keep the heap page-aligned, allocate an extra page before the heap.
 static const uint64_t SharedArrayMappedSize = AsmJSMappedSize + AsmJSPageSize;
 static_assert(sizeof(SharedArrayRawBuffer) < AsmJSPageSize, "Page size not big enough");
+
+// If there are too many 4GB buffers live we run up against system resource
+// exhaustion (address space or number of memory map descriptors), see
+// bug 1068684, bug 1073934 for details.  The limiting case seems to be
+// Windows Vista Home 64-bit, where the per-process address space is limited
+// to 8TB.  Thus we track the number of live objects, and set a limit of
+// 1000 live objects per process; we run synchronous GC if necessary; and
+// we throw an OOM error if the per-process limit is exceeded.
+static mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> numLive;
+static const uint32_t maxLive = 1000;
 #endif
 
 SharedArrayRawBuffer *
-SharedArrayRawBuffer::New(uint32_t length)
+SharedArrayRawBuffer::New(JSContext *cx, uint32_t length)
 {
     // The value (uint32_t)-1 is used as a signal in various places,
     // so guard against it on principle.
     MOZ_ASSERT(length != (uint32_t)-1);
 
     // Enforced by SharedArrayBufferObject::New(cx, length).
     MOZ_ASSERT(IsValidAsmJSHeapLength(length));
 
 #ifdef JS_CODEGEN_X64
+    // Test >= to guard against the case where multiple extant runtimes
+    // race to allocate.
+    if (++numLive >= maxLive) {
+        JSRuntime *rt = cx->runtime();
+        if (rt->largeAllocationFailureCallback)
+            rt->largeAllocationFailureCallback(rt->largeAllocationFailureCallbackData);
+        if (numLive >= maxLive) {
+            numLive--;
+            return nullptr;
+        }
+    }
     // Get the entire reserved region (with all pages inaccessible)
     void *p = MapMemory(SharedArrayMappedSize, false);
-    if (!p)
+    if (!p) {
+        numLive--;
         return nullptr;
+    }
 
     size_t validLength = AsmJSPageSize + length;
     if (!MarkValidRegion(p, validLength)) {
         UnmapMemory(p, SharedArrayMappedSize);
+        numLive--;
         return nullptr;
     }
 #   if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
     // Tell Valgrind/Memcheck to not report accesses in the inaccessible region.
     VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)p + validLength,
                                                    SharedArrayMappedSize-validLength);
 #   endif
 #else
@@ -129,16 +153,17 @@ SharedArrayRawBuffer::dropReference()
     // Drop the reference to the buffer.
     uint32_t refcount = --this->refcount; // Atomic.
 
     // If this was the final reference, release the buffer.
     if (refcount == 0) {
         uint8_t *p = this->dataPointer() - AsmJSPageSize;
         MOZ_ASSERT(uintptr_t(p) % AsmJSPageSize == 0);
 #ifdef JS_CODEGEN_X64
+        numLive--;
         UnmapMemory(p, SharedArrayMappedSize);
 #       if defined(MOZ_VALGRIND) \
            && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
         // Tell Valgrind/Memcheck to recommence reporting accesses in the
         // previously-inaccessible region.
         VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(p, SharedArrayMappedSize);
 #       endif
 #else
@@ -221,17 +246,17 @@ SharedArrayBufferObject::New(JSContext *
         else
             msg.reset(JS_smprintf("SharedArrayBuffer byteLength 0x%x is not a valid length. The next valid "
                                   "length is 0x%x", length, RoundUpToNextValidAsmJSHeapLength(length)));
         if (msg)
             JS_ReportError(cx, msg.get());
         return nullptr;
     }
 
-    SharedArrayRawBuffer *buffer = SharedArrayRawBuffer::New(length);
+    SharedArrayRawBuffer *buffer = SharedArrayRawBuffer::New(cx, length);
     if (!buffer)
         return nullptr;
 
     return New(cx, buffer);
 }
 
 SharedArrayBufferObject *
 SharedArrayBufferObject::New(JSContext *cx, SharedArrayRawBuffer *buffer)
--- a/js/src/vm/SharedArrayObject.h
+++ b/js/src/vm/SharedArrayObject.h
@@ -56,17 +56,17 @@ class SharedArrayRawBuffer
       : refcount(1),
         length(length),
         waiters_(nullptr)
     {
         MOZ_ASSERT(buffer == dataPointer());
     }
 
   public:
-    static SharedArrayRawBuffer *New(uint32_t length);
+    static SharedArrayRawBuffer *New(JSContext *cx, uint32_t length);
 
     // This may be called from multiple threads.  The caller must take
     // care of mutual exclusion.
     FutexWaiter* waiters() const {
         return waiters_;
     }
 
     // This may be called from multiple threads.  The caller must take