diff options
Diffstat (limited to 'src/mongo/scripting/mozjs/jscustomallocator.cpp')
-rw-r--r-- | src/mongo/scripting/mozjs/jscustomallocator.cpp | 234 |
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; +} |