summaryrefslogtreecommitdiff
path: root/src/mongo/scripting/mozjs/jscustomallocator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/scripting/mozjs/jscustomallocator.cpp')
-rw-r--r--src/mongo/scripting/mozjs/jscustomallocator.cpp234
1 files changed, 234 insertions, 0 deletions
diff --git a/src/mongo/scripting/mozjs/jscustomallocator.cpp b/src/mongo/scripting/mozjs/jscustomallocator.cpp
new file mode 100644
index 00000000000..adba220129d
--- /dev/null
+++ b/src/mongo/scripting/mozjs/jscustomallocator.cpp
@@ -0,0 +1,234 @@
+/**
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <cstddef>
+#include <type_traits>
+#include <jscustomallocator.h>
+
+#include "mongo/config.h"
+#include "mongo/util/concurrency/threadlocal.h"
+#include "mongo/scripting/mozjs/implscope.h"
+
+#ifdef __linux__
+#include <malloc.h>
+#elif defined(__APPLE__)
+#include <malloc/malloc.h>
+#elif defined(_WIN32)
+#include <malloc.h>
+#else
+#define MONGO_NO_MALLOC_USABLE_SIZE
+#endif
+
+/**
+ * This shim interface (which controls dynamic allocation within SpiderMonkey),
+ * consciously uses std::malloc and friends over mongoMalloc. It does this
+ * because SpiderMonkey has some plausible options in the event of OOM,
+ * specifically it can begin aggressive garbage collection. It would also be
+ * reasonable to go the other route and fail, but for the moment I erred on the
+ * side of maintaining the contract that SpiderMonkey expects.
+ *
+ * The overall strategy here is to keep track of allocations in a thread local,
+ * offering us the chance to enforce soft limits on memory use rather than
+ * waiting for the OS to OOM us.
+ */
+
+namespace mongo {
+namespace sm {
+
+namespace {
+/**
+ * These two 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.
+ */
+MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL size_t total_bytes;
+MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL size_t max_bytes;
+
+/**
+ * When we don't have malloc_usable_size, we manage by adjusting our pointer by
+ * kMaxAlign bytes and storing the size of the allocation kMaxAlign bytes
+ * behind the pointer we hand back. That let's us get to the value at runtime.
+ * We know kMaxAlign is enough (generally 8 or 16 bytes), because that's
+ * literally the contract between malloc and std::max_align_t.
+ *
+ * This is commented out right now because std::max_align_t didn't seem to be
+ * available on our solaris builder. TODO: revisit in the future to see if that
+ * still holds.
+ */
+// const size_t kMaxAlign = std::alignment_of<std::max_align_t>::value;
+const size_t kMaxAlign = 16;
+} // namespace
+
+size_t get_total_bytes() {
+ return total_bytes;
+}
+
+void reset(size_t bytes) {
+ total_bytes = 0;
+ max_bytes = bytes;
+}
+
+size_t get_max_bytes() {
+ return max_bytes;
+}
+
+/**
+ * Wraps std::Xalloc functions
+ *
+ * The idea here is to abstract soft limits on allocations, as well as possibly
+ * necessary pointer adjustment (if we don't have a malloc_usable_size
+ * replacement).
+ *
+ */
+template <typename T>
+void* wrap_alloc(T&& func, void* ptr, size_t bytes) {
+ size_t mb = get_max_bytes();
+ size_t tb = get_total_bytes();
+
+ if (mb && (tb + bytes > mb)) {
+ auto scope = mongo::mozjs::MozJSImplScope::getThreadScope();
+ invariant(scope);
+
+ scope->setOOM();
+
+ return nullptr;
+ }
+
+#ifdef MONGO_NO_MALLOC_USABLE_SIZE
+ void* p = func(ptr ? static_cast<char*>(ptr) - kMaxAlign : nullptr, bytes + kMaxAlign);
+#else
+ void* p = func(ptr, bytes);
+#endif
+
+ if (!p) {
+ return nullptr;
+ }
+
+#ifdef MONGO_NO_MALLOC_USABLE_SIZE
+ *reinterpret_cast<size_t*>(p) = bytes;
+ p = static_cast<char*>(p) + kMaxAlign;
+#endif
+
+ total_bytes = tb + bytes;
+
+ return p;
+}
+
+size_t get_current(void* ptr) {
+#ifdef MONGO_NO_MALLOC_USABLE_SIZE
+ if (!ptr)
+ return 0;
+
+ return *reinterpret_cast<size_t*>(static_cast<char*>(ptr) - kMaxAlign);
+#elif defined(__linux__)
+ return malloc_usable_size(ptr);
+#elif defined(__APPLE__)
+ return malloc_size(ptr);
+#elif defined(_WIN32)
+ return _msize(ptr);
+#else
+#error "Should be unreachable"
+#endif
+}
+
+} // namespace sm
+} // namespace mongo
+
+void* js_malloc(size_t bytes) {
+ return mongo::sm::wrap_alloc(
+ [](void* ptr, size_t b) { return std::malloc(b); }, nullptr, bytes);
+}
+
+void* js_calloc(size_t bytes) {
+ return mongo::sm::wrap_alloc(
+ [](void* ptr, size_t b) { return std::calloc(b, 1); }, nullptr, bytes);
+}
+
+void* js_calloc(size_t nmemb, size_t size) {
+ return mongo::sm::wrap_alloc(
+ [](void* ptr, size_t b) { return std::calloc(b, 1); }, nullptr, nmemb * size);
+}
+
+void js_free(void* p) {
+ if (!p)
+ return;
+
+ size_t current = mongo::sm::get_current(p);
+ size_t tb = mongo::sm::get_total_bytes();
+
+ if (tb >= current) {
+ mongo::sm::total_bytes = tb - current;
+ }
+
+ mongo::sm::wrap_alloc([](void* ptr, size_t b) {
+ std::free(ptr);
+ return nullptr;
+ }, p, 0);
+}
+
+void* js_realloc(void* p, size_t bytes) {
+ if (!p) {
+ return js_malloc(bytes);
+ }
+
+ if (!bytes) {
+ js_free(p);
+ return nullptr;
+ }
+
+ size_t current = mongo::sm::get_current(p);
+
+ if (current >= bytes) {
+ return p;
+ }
+
+ size_t tb = mongo::sm::total_bytes;
+
+ if (tb >= current) {
+ mongo::sm::total_bytes = tb - current;
+ }
+
+ return mongo::sm::wrap_alloc(
+ [](void* ptr, size_t b) { return std::realloc(ptr, b); }, p, bytes);
+}
+
+char* js_strdup(const char* s) {
+ size_t bytes = std::strlen(s) + 1;
+
+ char* new_s = static_cast<char*>(js_malloc(bytes));
+
+ if (!new_s) {
+ return nullptr;
+ }
+
+ std::memcpy(new_s, s, bytes);
+
+ return new_s;
+}