SERVER-126049: Use thread-local counter for mozjs mmap allocations (#53369)
GitOrigin-RevId: 27133c87a3234957a537cc0e82a983aba704b593
This commit is contained in:
parent
e67d85fc61
commit
eb8092bf50
@ -149,7 +149,7 @@
|
||||
},
|
||||
{
|
||||
"name": "import_script_path",
|
||||
"value": "src/third_party/mozjs/get-sources.sh"
|
||||
"value": "src/third_party/mozjs/scripts/import.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -3791,4 +3791,4 @@
|
||||
"dependsOn": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
264
jstests/noPassthrough/query/js/js_mozjs_heap_limit.js
Normal file
264
jstests/noPassthrough/query/js/js_mozjs_heap_limit.js
Normal file
@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Verifies that the per-scope JS heap limit (jsHeapLimitMB) does not produce false OOM errors
|
||||
* under concurrency, scope pool reuse, and mixed allocation patterns. Both
|
||||
* jsUseLegacyMemoryTracking modes must produce zero OOM errors.
|
||||
*
|
||||
* @tags: [
|
||||
* requires_scripting,
|
||||
* mozjs_wasm_unsupported,
|
||||
* ]
|
||||
*/
|
||||
import {Thread} from "jstests/libs/parallelTester.js";
|
||||
|
||||
const JS_HEAP_LIMIT_MB = 50;
|
||||
const conn = MongoRunner.runMongod({setParameter: {jsHeapLimitMB: JS_HEAP_LIMIT_MB}});
|
||||
const testDB = conn.getDB("js_mozjs_heap_limit");
|
||||
const coll = testDB.coll;
|
||||
|
||||
assert.commandWorked(coll.insertMany(Array.from({length: 100}, (_, i) => ({_id: i, n: 0}))));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Concurrent $where
|
||||
//
|
||||
// Each $where evaluation checks out a MozJSImplScope from the scope pool,
|
||||
// which owns a full SpiderMonkey JSContext. SpiderMonkey backs its GC arena
|
||||
// with mmap'd 1 MB chunks (MapAlignedPages -> record_mmap_alloc); a scope
|
||||
// evaluating a trivial predicate like this one uses ~5 MB of mmap.
|
||||
//
|
||||
// Overcounting risk: if the mmap counter is not scoped per-thread, concurrent
|
||||
// scopes inflate each other's apparent usage. With 16 threads each holding
|
||||
// ~5 MB of GC arena, a shared counter would show ~80 MB per scope -- well
|
||||
// over the 50 MB jsHeapLimitMB -- causing ExceededMemoryLimit on scope init
|
||||
// even though each individual scope is using only ~5 MB (10% of the limit).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function concurrentWorker(host, dbName, iterations) {
|
||||
const db = new Mongo(host).getDB(dbName);
|
||||
let oom = 0,
|
||||
other = 0,
|
||||
ok = 0;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
try {
|
||||
const res = db.runCommand({
|
||||
findAndModify: "coll",
|
||||
query: {_id: Math.floor(Math.random() * 100), $where: "this._id !== undefined"},
|
||||
update: {$inc: {n: 1}},
|
||||
});
|
||||
res.ok === 1
|
||||
? ok++
|
||||
: res.code === ErrorCodes.ExceededMemoryLimit || res.code === ErrorCodes.JSInterpreterFailure
|
||||
? oom++
|
||||
: other++;
|
||||
} catch (e) {
|
||||
e.code === ErrorCodes.ExceededMemoryLimit || e.code === ErrorCodes.JSInterpreterFailure ? oom++ : other++;
|
||||
}
|
||||
}
|
||||
return {oom, other, ok};
|
||||
}
|
||||
|
||||
function runConcurrent(threads, iterations) {
|
||||
const ts = Array.from(
|
||||
{length: threads},
|
||||
() => new Thread(concurrentWorker, conn.host, testDB.getName(), iterations),
|
||||
);
|
||||
ts.forEach((t) => t.start());
|
||||
return ts.reduce(
|
||||
(acc, t) => {
|
||||
t.join();
|
||||
const r = t.returnData();
|
||||
return {oom: acc.oom + r.oom, other: acc.other + r.other, ok: acc.ok + r.ok};
|
||||
},
|
||||
{oom: 0, other: 0, ok: 0},
|
||||
);
|
||||
}
|
||||
|
||||
for (const legacy of [false, true]) {
|
||||
jsTest.log.info(`Phase 1: concurrent $where, jsUseLegacyMemoryTracking=${legacy}`);
|
||||
assert.commandWorked(testDB.adminCommand({setParameter: 1, jsUseLegacyMemoryTracking: legacy}));
|
||||
const r = runConcurrent(16, 5);
|
||||
assert.eq(r.oom, 0, `jsUseLegacyMemoryTracking=${legacy}: unexpected OOM errors: ${tojson(r)}`);
|
||||
assert.eq(r.other, 0, `jsUseLegacyMemoryTracking=${legacy}: unexpected errors: ${tojson(r)}`);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scope pool reuse: sequential queries must not accumulate mmap_bytes
|
||||
//
|
||||
// $function checks out a scope from MozJSProxyScope's pool and returns it
|
||||
// after each aggregation completes. new ArrayBuffer(1 MB) allocates its
|
||||
// backing store through SpiderMonkey's BufferAllocator, which calls mmap
|
||||
// directly (not the GC arena) and is tracked via record_mmap_alloc. When
|
||||
// the scope is returned to the pool a GC runs and collects the ArrayBuffer;
|
||||
// its finalizer calls UnmapPages -> record_mmap_free, driving mmap_bytes
|
||||
// back to zero before the next checkout.
|
||||
//
|
||||
// Overcounting risk: if record_mmap_free is not called when the buffer is
|
||||
// collected, or if the counter is not correctly associated with the scope's
|
||||
// thread, mmap_bytes accumulates across reuse cycles. Per-iteration peak is
|
||||
// ~5 MB. With a 50 MB limit, unchecked accumulation would cross the limit
|
||||
// after ~9 additional iterations. Running 15 iterations gives clear headroom
|
||||
// above the failure point while keeping the test fast.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
jsTest.log.info("Phase 2: scope pool reuse, sequential mmap_bytes accumulation check");
|
||||
assert.commandWorked(testDB.adminCommand({setParameter: 1, jsUseLegacyMemoryTracking: false}));
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const res = coll
|
||||
.aggregate([
|
||||
{$limit: 1},
|
||||
{
|
||||
$project: {
|
||||
r: {
|
||||
$function: {
|
||||
body: function () {
|
||||
// ArrayBuffer backing store -> BufferAllocator -> mmap -> record_mmap_alloc.
|
||||
// Freed via UnmapPages -> record_mmap_free when the GC collects this buffer on scope return.
|
||||
let b = new ArrayBuffer(1024 * 1024);
|
||||
return new Uint8Array(b)[0];
|
||||
},
|
||||
args: [],
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
assert.eq(res.length, 1, `scope reuse iteration ${i} failed`);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mixed allocations: arena objects + BufferAllocator mmap in same scope
|
||||
//
|
||||
// Exercises two allocation paths within a single $function invocation:
|
||||
// - 100 plain JS objects {x: j}: GC arena objects, bump-allocated from
|
||||
// mmap'd arena chunks. Creating many short-lived objects
|
||||
// triggers GC, exercising the record_mmap_free path as chunks are swept.
|
||||
// - new Uint8Array(200 * 1024): the 200 KB typed-array backing store is a
|
||||
// medium BufferAllocator allocation, sub-allocated from a mmap'd
|
||||
// BufferChunk.
|
||||
//
|
||||
// Overcounting risk: if mmap_bytes leaks across scope boundaries under
|
||||
// concurrent load, the apparent total inflates. Per-scope total is ~5 MB
|
||||
// << 50 MB limit, so any OOM error indicates a counting error rather than
|
||||
// genuine memory pressure.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function mixedWorker(host, dbName, iterations) {
|
||||
const db = new Mongo(host).getDB(dbName);
|
||||
let oom = 0,
|
||||
other = 0;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
try {
|
||||
db.coll
|
||||
.aggregate([
|
||||
{$limit: 1},
|
||||
{
|
||||
$project: {
|
||||
r: {
|
||||
$function: {
|
||||
body: function () {
|
||||
// Plain JS objects: GC arena (malloc_bytes).
|
||||
let s = [];
|
||||
for (let j = 0; j < 100; j++) s.push({x: j});
|
||||
// Typed array backing store: BufferAllocator mmap (mmap_bytes).
|
||||
return s.length + new Uint8Array(200 * 1024)[0];
|
||||
},
|
||||
args: [],
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
} catch (e) {
|
||||
e.code === ErrorCodes.ExceededMemoryLimit || e.code === ErrorCodes.JSInterpreterFailure ? oom++ : other++;
|
||||
}
|
||||
}
|
||||
return {oom, other};
|
||||
}
|
||||
|
||||
jsTest.log.info("Phase 3: concurrent GC pressure + BufferAllocator mmap");
|
||||
assert.commandWorked(testDB.adminCommand({setParameter: 1, jsUseLegacyMemoryTracking: false}));
|
||||
const mixedTs = Array.from({length: 8}, () => new Thread(mixedWorker, conn.host, testDB.getName(), 3));
|
||||
mixedTs.forEach((t) => t.start());
|
||||
const mixed = mixedTs.reduce(
|
||||
(acc, t) => {
|
||||
t.join();
|
||||
const r = t.returnData();
|
||||
return {oom: acc.oom + r.oom, other: acc.other + r.other};
|
||||
},
|
||||
{oom: 0, other: 0},
|
||||
);
|
||||
assert.eq(mixed.oom, 0, `mixed allocation test: unexpected OOM errors: ${tojson(mixed)}`);
|
||||
assert.eq(mixed.other, 0, `mixed allocation test: unexpected errors: ${tojson(mixed)}`);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// malloc_bytes stress: long strings allocated via StringBufferArena
|
||||
//
|
||||
// SpiderMonkey stores long string character data in a separate heap buffer
|
||||
// allocated through StringBufferArena -> js_arena_malloc -> wrap_alloc ->
|
||||
// malloc_bytes. Strings longer than ~23 chars exceed the inline limit and
|
||||
// always use this path. 10,000 Latin-1 strings of 1000 chars = ~10 MB of
|
||||
// malloc_bytes per scope. Combined with the ~5 MB GC arena baseline in
|
||||
// mmap_bytes, the per-scope total is ~15 MB, well under the 50 MB limit.
|
||||
//
|
||||
// Overcounting risk: if malloc_bytes is not scoped per-thread, concurrent
|
||||
// scopes inflate each other's apparent usage. With 8 threads each holding
|
||||
// ~10 MB of string data, a shared counter would show ~80 MB per scope --
|
||||
// far exceeding the 50 MB jsHeapLimitMB.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function mallocWorker(host, dbName, iterations) {
|
||||
const db = new Mongo(host).getDB(dbName);
|
||||
let oom = 0,
|
||||
other = 0;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
try {
|
||||
db.coll
|
||||
.aggregate([
|
||||
{$limit: 1},
|
||||
{
|
||||
$project: {
|
||||
r: {
|
||||
$function: {
|
||||
body: function () {
|
||||
// Long strings: char data -> StringBufferArena -> js_malloc -> malloc_bytes.
|
||||
// 10,000 x 1000-char Latin-1 strings = ~10 MB of malloc_bytes.
|
||||
let strs = [];
|
||||
for (let j = 0; j < 10000; j++) {
|
||||
strs.push("x".repeat(1000));
|
||||
}
|
||||
return strs.length;
|
||||
},
|
||||
args: [],
|
||||
lang: "js",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
} catch (e) {
|
||||
e.code === ErrorCodes.ExceededMemoryLimit || e.code === ErrorCodes.JSInterpreterFailure ? oom++ : other++;
|
||||
}
|
||||
}
|
||||
return {oom, other};
|
||||
}
|
||||
|
||||
jsTest.log.info("Phase 4: concurrent malloc_bytes stress via long string allocations");
|
||||
assert.commandWorked(testDB.adminCommand({setParameter: 1, jsUseLegacyMemoryTracking: false}));
|
||||
const mallocTs = Array.from({length: 8}, () => new Thread(mallocWorker, conn.host, testDB.getName(), 3));
|
||||
mallocTs.forEach((t) => t.start());
|
||||
const mallocResult = mallocTs.reduce(
|
||||
(acc, t) => {
|
||||
t.join();
|
||||
const r = t.returnData();
|
||||
return {oom: acc.oom + r.oom, other: acc.other + r.other};
|
||||
},
|
||||
{oom: 0, other: 0},
|
||||
);
|
||||
assert.eq(mallocResult.oom, 0, `malloc stress test: unexpected OOM errors: ${tojson(mallocResult)}`);
|
||||
assert.eq(mallocResult.other, 0, `malloc stress test: unexpected errors: ${tojson(mallocResult)}`);
|
||||
|
||||
MongoRunner.stopMongod(conn);
|
||||
@ -114,7 +114,7 @@
|
||||
},
|
||||
{
|
||||
"name": "import_script_path",
|
||||
"value": "src/third_party/mozjs/get-sources.sh"
|
||||
"value": "src/third_party/mozjs/scripts/import.sh"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -2834,4 +2834,4 @@
|
||||
"dependsOn": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,5 +238,93 @@ TEST_F(JSCustomAllocatorTest, OOMDuringScopeInitDoesNotCrash) {
|
||||
}
|
||||
#endif // MONGO_CONFIG_DEBUG_BUILD
|
||||
|
||||
// With tracking disabled, record_mmap_alloc should be a no-op.
|
||||
TEST_F(JSCustomAllocatorTest, MmapTrackingDisabledByDefault) {
|
||||
mongo::sm::reset(0, false);
|
||||
mongo::sm::record_mmap_alloc(1024);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 0);
|
||||
ASSERT_EQUALS(mongo::sm::get_total_bytes(), 0);
|
||||
}
|
||||
|
||||
// Basic alloc/free round-trip with mmap tracking enabled.
|
||||
TEST_F(JSCustomAllocatorTest, MmapAllocAndFree) {
|
||||
// A non-zero limit is required; record_mmap_alloc no-ops when max_bytes == 0.
|
||||
mongo::sm::reset(10 * 1024 * 1024, true);
|
||||
mongo::sm::record_mmap_alloc(1024 * 1024);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 1024 * 1024);
|
||||
ASSERT_EQUALS(mongo::sm::get_total_bytes(), 1024 * 1024);
|
||||
|
||||
mongo::sm::record_mmap_free(1024 * 1024);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 0);
|
||||
ASSERT_EQUALS(mongo::sm::get_total_bytes(), 0);
|
||||
}
|
||||
|
||||
// mmap bytes count towards the shared total and can trigger OOM (signal_oom is
|
||||
// a safe no-op in unit-test context where no JSContext is running).
|
||||
TEST_F(JSCustomAllocatorTest, MmapCountedTowardsLimit) {
|
||||
constexpr size_t kLimit = 2 * 1024 * 1024; // 2 MB
|
||||
mongo::sm::reset(kLimit, true);
|
||||
|
||||
// A malloc allocation of 0.5 MB should succeed and register in the total.
|
||||
void* ptr = js_malloc(512 * 1024);
|
||||
ASSERT_NOT_EQUALS(ptr, nullptr);
|
||||
|
||||
// First mmap alloc (1 MB): total is now ~1.5 MB, still under the 2 MB limit.
|
||||
mongo::sm::record_mmap_alloc(1024 * 1024);
|
||||
ASSERT_GREATER_THAN_OR_EQUALS(mongo::sm::get_total_bytes(), (1024 * 1024) + (512 * 1024));
|
||||
|
||||
// Second mmap alloc (1 MB): pushes total over 2 MB limit. signal_oom() is
|
||||
// called internally but is a safe no-op here; the counter still increments.
|
||||
mongo::sm::record_mmap_alloc(1024 * 1024);
|
||||
ASSERT_GREATER_THAN(mongo::sm::get_total_bytes(), kLimit);
|
||||
|
||||
// Clean up.
|
||||
mongo::sm::record_mmap_free(2 * 1024 * 1024);
|
||||
js_free(ptr);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 0);
|
||||
ASSERT_EQUALS(mongo::sm::get_total_bytes(), 0);
|
||||
}
|
||||
|
||||
// malloc_bytes and mmap_bytes are tracked independently; get_total_bytes() is
|
||||
// their sum.
|
||||
TEST_F(JSCustomAllocatorTest, MmapAndMallocIndependent) {
|
||||
mongo::sm::reset(10 * 1024 * 1024, true);
|
||||
|
||||
void* ptr = js_malloc(64 * 1024);
|
||||
ASSERT_NOT_EQUALS(ptr, nullptr);
|
||||
ASSERT_GREATER_THAN(mongo::sm::get_malloc_bytes(), 0);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 0);
|
||||
|
||||
mongo::sm::record_mmap_alloc(128 * 1024);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 128 * 1024);
|
||||
ASSERT_EQUALS(mongo::sm::get_total_bytes(),
|
||||
mongo::sm::get_malloc_bytes() + mongo::sm::get_mmap_bytes());
|
||||
|
||||
mongo::sm::record_mmap_free(128 * 1024);
|
||||
js_free(ptr);
|
||||
ASSERT_EQUALS(mongo::sm::get_malloc_bytes(), 0);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 0);
|
||||
ASSERT_EQUALS(mongo::sm::get_total_bytes(), 0);
|
||||
}
|
||||
|
||||
// Toggling mmap tracking off via reset() suppresses subsequent record_mmap_alloc calls.
|
||||
TEST_F(JSCustomAllocatorTest, MmapTrackingToggle) {
|
||||
// Enable tracking with a non-zero limit so record_mmap_alloc actually counts.
|
||||
mongo::sm::reset(10 * 1024 * 1024, true);
|
||||
mongo::sm::record_mmap_alloc(1024);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 1024);
|
||||
|
||||
// Free before reset — reset() asserts mmap_bytes == 0 as a production invariant
|
||||
// (it is only called when a new JS runtime is created, at which point all prior
|
||||
// allocations must already have been freed by the shutdown GC).
|
||||
mongo::sm::record_mmap_free(1024);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 0);
|
||||
|
||||
// Toggle tracking off; subsequent alloc calls should be ignored.
|
||||
mongo::sm::reset(0, false);
|
||||
mongo::sm::record_mmap_alloc(2048);
|
||||
ASSERT_EQUALS(mongo::sm::get_mmap_bytes(), 0);
|
||||
}
|
||||
|
||||
} // namespace mozjs
|
||||
} // namespace mongo
|
||||
|
||||
@ -387,6 +387,8 @@ void MozJSImplScope::_gcCallback(JSContext* rt,
|
||||
"phase"_attr = (status == JSGC_BEGIN ? "prologue" : "epilogue"),
|
||||
"reason"_attr = ExplainGCReason(reason),
|
||||
"total"_attr = mongo::sm::get_total_bytes(),
|
||||
"malloc"_attr = mongo::sm::get_malloc_bytes(),
|
||||
"mmap"_attr = mongo::sm::get_mmap_bytes(),
|
||||
"limit"_attr = mongo::sm::get_max_bytes(),
|
||||
"gc total"_attr = js::GetGCHeapUsage(rt),
|
||||
"gc bytes"_attr = JS_GetGCParameter(rt, JSGC_BYTES),
|
||||
@ -551,6 +553,15 @@ MozJSImplScope::MozRuntime::MozRuntime(const MozJSScriptEngine* engine,
|
||||
}
|
||||
}
|
||||
|
||||
MozJSImplScope::MozRuntime::~MozRuntime() {
|
||||
// Explicitly destroy the context before checking mmap_bytes. This should run JS_DestroyContext
|
||||
// and the shutdown GC that unmaps all GC chunks. After context destructs all GC chunks should
|
||||
// be unmapped and mmap_bytes should be 0.
|
||||
_context.reset();
|
||||
MOZ_ASSERT(mongo::sm::get_mmap_bytes() == 0,
|
||||
"Expected all GC memory to be unmapped by MozRuntime shutdown");
|
||||
}
|
||||
|
||||
MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine, boost::optional<int> jsHeapLimitMB)
|
||||
: _engine(engine),
|
||||
_mr(engine, jsHeapLimitMB),
|
||||
|
||||
@ -572,6 +572,7 @@ private:
|
||||
struct MozRuntime {
|
||||
public:
|
||||
MozRuntime(const MozJSScriptEngine* engine, boost::optional<int> jsHeapLimitMB);
|
||||
~MozRuntime();
|
||||
std::unique_ptr<JSContext, std::function<void(JSContext*)>> _context;
|
||||
};
|
||||
|
||||
|
||||
@ -70,11 +70,18 @@ namespace sm {
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* These two variables track the total number of bytes handed out, and the
|
||||
* These variables track the total number of bytes handed out, and the
|
||||
* maximum number of bytes we will consider handing out. They are set by
|
||||
* MozJSImplScope on start up.
|
||||
*
|
||||
* IMPORTANT: This tracking is accurate as long as all the allocations for a single scope are run
|
||||
* from the same thread. We enforce this via js::DisableExtraThreads(). However if this changed and
|
||||
* mozjs GC background threads were allowed to run, deallocations could be issued from a different
|
||||
* thread than the thread that made the allocation, causing these counters to become inaccurate on
|
||||
* the main scope thread.
|
||||
*/
|
||||
thread_local size_t malloc_bytes = 0;
|
||||
thread_local size_t mmap_bytes = 0;
|
||||
thread_local size_t max_bytes = 0;
|
||||
|
||||
// Allow disabling mmap tracking as a fallback in case counted memory goes too high
|
||||
@ -102,11 +109,15 @@ size_t get_malloc_bytes() {
|
||||
}
|
||||
|
||||
size_t get_mmap_bytes() {
|
||||
return track_mmap_bytes ? js::gc::GetProfilerMemoryCounts().bytes : 0;
|
||||
return track_mmap_bytes ? mmap_bytes : 0;
|
||||
}
|
||||
|
||||
void reset(size_t bytes, bool track_mmap) {
|
||||
malloc_bytes = 0;
|
||||
|
||||
MOZ_ASSERT(mmap_bytes == 0, "Expected mmap_bytes to be 0 during runtime initialization");
|
||||
mmap_bytes = 0;
|
||||
|
||||
max_bytes = bytes;
|
||||
track_mmap_bytes = track_mmap;
|
||||
}
|
||||
@ -122,12 +133,18 @@ void signal_oom() {
|
||||
}
|
||||
}
|
||||
|
||||
void check_oom_on_mmap_allocation(size_t bytes) {
|
||||
// called after an mmap allocation to check whether an oom signal is required
|
||||
// if track_mmap_bytes is disabled we fall back to legacy logic and completely bypass this path
|
||||
/**
|
||||
* Called after an mmap allocation to update memory bookkeeping and check whether an oom signal is
|
||||
* required. If track_mmap_bytes is disabled we fall back to legacy logic and completely bypass this
|
||||
* path
|
||||
*/
|
||||
void record_mmap_alloc(size_t bytes) {
|
||||
mmap_bytes += bytes;
|
||||
|
||||
size_t mb = get_max_bytes();
|
||||
size_t tb = get_total_bytes();
|
||||
if (track_mmap_bytes && mb > 0 && get_total_bytes() + bytes > mb) {
|
||||
|
||||
if (track_mmap_bytes && mb > 0 && tb > mb) {
|
||||
LOGV2(10920001,
|
||||
"Out of memory on MozJS mmap allocation",
|
||||
"bytes"_attr = bytes,
|
||||
@ -137,6 +154,11 @@ void check_oom_on_mmap_allocation(size_t bytes) {
|
||||
}
|
||||
}
|
||||
|
||||
void record_mmap_free(size_t bytes) {
|
||||
MOZ_ASSERT(bytes <= mmap_bytes, "Freed mmap byte count exceeds tracked allocated bytes");
|
||||
mmap_bytes -= std::min(mmap_bytes, bytes);
|
||||
}
|
||||
|
||||
size_t get_current(void* ptr);
|
||||
|
||||
/**
|
||||
|
||||
@ -49,7 +49,8 @@ extern MOZ_NORETURN MOZ_COLD JS_PUBLIC_API void JS_Assert(const char* s,
|
||||
*/
|
||||
namespace mongo {
|
||||
namespace sm {
|
||||
JS_PUBLIC_API void check_oom_on_mmap_allocation(size_t bytes);
|
||||
JS_PUBLIC_API void record_mmap_alloc(size_t bytes);
|
||||
JS_PUBLIC_API void record_mmap_free(size_t bytes);
|
||||
} // namespace sm
|
||||
} // namespace mongo
|
||||
|
||||
|
||||
@ -1143,13 +1143,13 @@ void RecordMemoryAlloc(size_t bytes) {
|
||||
MOZ_ASSERT(bytes);
|
||||
MOZ_ASSERT((bytes % pageSize) == 0);
|
||||
|
||||
// MONGODB MODIFICATION: Check whether this allocation took us over a
|
||||
// configured memory limit. This may trigger OOM in the background, but we
|
||||
// allow the mozjs logic to proceed normally for now
|
||||
mongo::sm::check_oom_on_mmap_allocation(bytes);
|
||||
|
||||
gMappedMemorySizeBytes += bytes;
|
||||
gMappedMemoryOperations++;
|
||||
|
||||
// MONGODB MODIFICATION: Update memory bookkeeping. This may check whether the
|
||||
// allocation took us over a configured memory limit and trigger OOM in the
|
||||
// background. Either way, we allow the allocation to proceed normally for now
|
||||
mongo::sm::record_mmap_alloc(bytes);
|
||||
}
|
||||
|
||||
void RecordMemoryFree(size_t bytes) {
|
||||
@ -1159,6 +1159,9 @@ void RecordMemoryFree(size_t bytes) {
|
||||
|
||||
gMappedMemorySizeBytes -= bytes;
|
||||
gMappedMemoryOperations++;
|
||||
|
||||
// MONGODB MODIFICATION: Update memory bookkeeping
|
||||
mongo::sm::record_mmap_free(bytes);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API ProfilerMemoryCounts GetProfilerMemoryCounts() {
|
||||
|
||||
@ -98,7 +98,8 @@ void InitLargeAllocLimit() {
|
||||
// allocator implementation in mongo_sources.
|
||||
#ifndef JS_USE_CUSTOM_ALLOCATOR
|
||||
|
||||
JS_PUBLIC_API void mongo::sm::check_oom_on_mmap_allocation(size_t bytes) {}
|
||||
JS_PUBLIC_API void mongo::sm::record_mmap_alloc(size_t bytes) {}
|
||||
JS_PUBLIC_API void mongo::sm::record_mmap_free(size_t bytes) {}
|
||||
|
||||
JS_PUBLIC_DATA arena_id_t js::MallocArena;
|
||||
JS_PUBLIC_DATA arena_id_t js::BackgroundMallocArena;
|
||||
|
||||
3
src/third_party/mozjs/include/js/Utility.h
vendored
3
src/third_party/mozjs/include/js/Utility.h
vendored
@ -49,7 +49,8 @@ extern MOZ_NORETURN MOZ_COLD JS_PUBLIC_API void JS_Assert(const char* s,
|
||||
*/
|
||||
namespace mongo {
|
||||
namespace sm {
|
||||
JS_PUBLIC_API void check_oom_on_mmap_allocation(size_t bytes);
|
||||
JS_PUBLIC_API void record_mmap_alloc(size_t bytes);
|
||||
JS_PUBLIC_API void record_mmap_free(size_t bytes);
|
||||
} // namespace sm
|
||||
} // namespace mongo
|
||||
|
||||
|
||||
@ -39,7 +39,8 @@ JS_PUBLIC_API void reset(size_t max_bytes, bool track_mmap);
|
||||
JS_PUBLIC_API size_t get_total_bytes();
|
||||
JS_PUBLIC_API size_t get_max_bytes();
|
||||
|
||||
JS_PUBLIC_API void check_oom_on_mmap_allocation(size_t bytes);
|
||||
JS_PUBLIC_API void record_mmap_alloc(size_t bytes);
|
||||
JS_PUBLIC_API void record_mmap_free(size_t bytes);
|
||||
|
||||
size_t get_malloc_bytes();
|
||||
size_t get_mmap_bytes();
|
||||
|
||||
@ -10,10 +10,10 @@ NAME=spidermonkey
|
||||
|
||||
VERSION="140.9.0esr"
|
||||
LIB_GIT_BRANCH=spidermonkey-esr140.9-cpp-only
|
||||
LIB_GIT_REVISION=06ac22a94df3a53a8959a1ef10f8fcda99a1867e
|
||||
LIB_GIT_REVISION=dd4a007275927572d0b10857d3f11b28550d0095
|
||||
LIB_GIT_REPO=git@github.com:mongodb-forks/spidermonkey.git
|
||||
# If a local spidermonkey repo exists, this is much faster than fetching from git:
|
||||
# LIB_GIT_REPO=/home/ubuntu/spidermonkey/.git
|
||||
# If a local spidermonkey repo exists, this can be faster than fetching from github:
|
||||
# LIB_GIT_REPO=file:///home/ubuntu/spidermonkey/.git
|
||||
|
||||
DEST_DIR=$(git rev-parse --show-toplevel)/src/third_party/mozjs
|
||||
|
||||
@ -23,9 +23,8 @@ LIB_GIT_DIR=$(mktemp -d /tmp/import-spidermonkey.XXXXXX)
|
||||
trap "rm -rf $LIB_GIT_DIR" EXIT
|
||||
|
||||
git clone \
|
||||
--branch "$LIB_GIT_BRANCH" \
|
||||
--revision "$LIB_GIT_REVISION" \
|
||||
--depth 1 \
|
||||
--single-branch \
|
||||
"$LIB_GIT_REPO" \
|
||||
"$LIB_GIT_DIR"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user