From ab1bf6715e09c7826389f6d927ef39c86e1156ed Mon Sep 17 00:00:00 2001 From: Philip Pronin Date: Tue, 13 Dec 2016 01:53:43 -0800 Subject: [PATCH] move JemallocNodumpAllocator to folly/experimental Summary: Moving russoue's `JemallocNodumpAllocator` (D3147173) from `mcrouter` codebase to `folly`, so we can use it in a few other services that suffer from a huge core dump problem. Reviewed By: russoue, jmswen, luciang Differential Revision: D4311394 fbshipit-source-id: 6a13c478b939bd411e0fd37e655125f62434c366 --- folly/Makefile.am | 2 + .../experimental/JemallocNodumpAllocator.cpp | 119 ++++++++++++++++++ folly/experimental/JemallocNodumpAllocator.h | 102 +++++++++++++++ .../test/JemallocNodumpAllocatorTest.cpp | 61 +++++++++ 4 files changed, 284 insertions(+) create mode 100644 folly/experimental/JemallocNodumpAllocator.cpp create mode 100644 folly/experimental/JemallocNodumpAllocator.h create mode 100644 folly/experimental/test/JemallocNodumpAllocatorTest.cpp diff --git a/folly/Makefile.am b/folly/Makefile.am index d797e082..92bf03a0 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -107,6 +107,7 @@ nobase_follyinclude_HEADERS = \ experimental/FunctionScheduler.h \ experimental/FutureDAG.h \ experimental/io/FsUtil.h \ + experimental/JemallocNodumpAllocator.h \ experimental/JSONSchema.h \ experimental/LockFreeRingBuffer.h \ experimental/NestedCommandLineApp.h \ @@ -508,6 +509,7 @@ libfolly_la_SOURCES = \ experimental/DynamicParser.cpp \ experimental/FunctionScheduler.cpp \ experimental/io/FsUtil.cpp \ + experimental/JemallocNodumpAllocator.cpp \ experimental/JSONSchema.cpp \ experimental/NestedCommandLineApp.cpp \ experimental/observer/detail/Core.cpp \ diff --git a/folly/experimental/JemallocNodumpAllocator.cpp b/folly/experimental/JemallocNodumpAllocator.cpp new file mode 100644 index 00000000..6286d1bf --- /dev/null +++ b/folly/experimental/JemallocNodumpAllocator.cpp @@ -0,0 +1,119 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +namespace folly { + +JemallocNodumpAllocator::JemallocNodumpAllocator(State state) { + if (state == State::ENABLED && extend_and_setup_arena()) { + LOG(INFO) << "Set up arena: " << arena_index_; + } +} + +bool JemallocNodumpAllocator::extend_and_setup_arena() { +#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + if (mallctl == nullptr) { + // Not linked with jemalloc. + return false; + } + + size_t len = sizeof(arena_index_); + if (auto ret = mallctl("arenas.extend", &arena_index_, &len, nullptr, 0)) { + LOG(FATAL) << "Unable to extend arena: " << errnoStr(ret); + } + flags_ = MALLOCX_ARENA(arena_index_) | MALLOCX_TCACHE_NONE; + + // Set the custom alloc hook + const auto key = + folly::to("arena.", arena_index_, ".chunk_hooks"); + chunk_hooks_t hooks; + len = sizeof(hooks); + // Read the existing hooks + if (auto ret = mallctl(key.c_str(), &hooks, &len, nullptr, 0)) { + LOG(FATAL) << "Unable to get the hooks: " << errnoStr(ret); + } + if (original_chunk_alloc_ == nullptr) { + original_chunk_alloc_ = hooks.alloc; + } else { + DCHECK_EQ(original_chunk_alloc_, hooks.alloc); + } + + // Set the custom hook + hooks.alloc = &JemallocNodumpAllocator::chunk_alloc; + if (auto ret = + mallctl(key.c_str(), nullptr, nullptr, &hooks, sizeof(hooks))) { + LOG(FATAL) << "Unable to set the hooks: " << errnoStr(ret); + } + + return true; +#else // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + return false; +#endif // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED +} + +void* JemallocNodumpAllocator::allocate(size_t size) { + return mallocx != nullptr ? mallocx(size, flags_) : malloc(size); +} + +void* JemallocNodumpAllocator::reallocate(void* p, size_t size) { + return rallocx != nullptr ? rallocx(p, size, flags_) : realloc(p, size); +} + +#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + +chunk_alloc_t* JemallocNodumpAllocator::original_chunk_alloc_ = nullptr; + +void* JemallocNodumpAllocator::chunk_alloc( + void* chunk, + size_t size, + size_t alignment, + bool* zero, + bool* commit, + unsigned arena_ind) { + void* result = + original_chunk_alloc_(chunk, size, alignment, zero, commit, arena_ind); + if (result != nullptr) { + if (auto ret = madvise(result, size, MADV_DONTDUMP)) { + VLOG(1) << "Unable to madvise(MADV_DONTDUMP): " << errnoStr(ret); + } + } + + return result; +} + +#endif // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + +void JemallocNodumpAllocator::deallocate(void* p) { + dallocx != nullptr ? dallocx(p, flags_) : free(p); +} + +void JemallocNodumpAllocator::deallocate(void* p, void* userData) { + const uint64_t flags = reinterpret_cast(userData); + dallocx != nullptr ? dallocx(p, flags) : free(p); +} + +JemallocNodumpAllocator& globalJemallocNodumpAllocator() { + static auto instance = new JemallocNodumpAllocator(); + return *instance; +} + +} // folly diff --git a/folly/experimental/JemallocNodumpAllocator.h b/folly/experimental/JemallocNodumpAllocator.h new file mode 100644 index 00000000..e12454c3 --- /dev/null +++ b/folly/experimental/JemallocNodumpAllocator.h @@ -0,0 +1,102 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// http://www.canonware.com/download/jemalloc/jemalloc-latest/doc/jemalloc.html + +#pragma once + +#include + +#ifdef FOLLY_HAVE_LIBJEMALLOC + +#include +#include + +#if (JEMALLOC_VERSION_MAJOR > 3) && defined(MADV_DONTDUMP) +#define FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED 1 +#endif + +#endif // FOLLY_HAVE_LIBJEMALLOC + +#include + +namespace folly { + +/** + * An allocator which uses Jemalloc to create an dedicated arena to allocate + * memory from. The only special property set on the allocated memory is that + * the memory is not dump-able. + * + * This is done by setting MADV_DONTDUMP using the `madvise` system call. A + * custom hook installed which is called when allocating a new chunk of memory. + * All it does is call the original jemalloc hook to allocate the memory and + * then set the advise on it before returning the pointer to the allocated + * memory. Jemalloc does not use allocated chunks across different arenas, + * without `munmap`-ing them first, and the advises are not sticky i.e. they are + * unset if `munmap` is done. Also this arena can't be used by any other part of + * the code by just calling `malloc`. + * + * If target system doesn't support MADV_DONTDUMP or jemalloc doesn't support + * custom arena hook, JemallocNodumpAllocator would fall back to using malloc / + * free. Such behavior can be identified by using + * !defined(FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED). + * + * Similarly, if binary isn't linked with jemalloc, the logic would fall back to + * malloc / free. + */ +class JemallocNodumpAllocator { + public: + enum class State { + ENABLED, + DISABLED, + }; + + // To be used as IOBuf::FreeFunction, userData should be set to + // reinterpret_cast(getFlags()). + static void deallocate(void* p, void* userData); + + explicit JemallocNodumpAllocator(State state = State::ENABLED); + + void* allocate(size_t size); + void* reallocate(void* p, size_t size); + void deallocate(void* p); + + unsigned getArenaIndex() const { return arena_index_; } + int getFlags() const { return flags_; } + + private: +#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + static chunk_alloc_t* original_chunk_alloc_; + static void* chunk_alloc(void* chunk, + size_t size, + size_t alignment, + bool* zero, + bool* commit, + unsigned arena_ind); +#endif // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + + bool extend_and_setup_arena(); + + unsigned arena_index_{0}; + int flags_{0}; +}; + +/** + * JemallocNodumpAllocator singleton. + */ +JemallocNodumpAllocator& globalJemallocNodumpAllocator(); + +} // folly diff --git a/folly/experimental/test/JemallocNodumpAllocatorTest.cpp b/folly/experimental/test/JemallocNodumpAllocatorTest.cpp new file mode 100644 index 00000000..008a640c --- /dev/null +++ b/folly/experimental/test/JemallocNodumpAllocatorTest.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +TEST(JemallocNodumpAllocatorTest, Basic) { + folly::JemallocNodumpAllocator jna; + +#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + if (folly::usingJEMalloc()) { + EXPECT_NE(0, jna.getArenaIndex()); + } +#endif // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + + auto ptr = jna.allocate(1024); + EXPECT_NE(nullptr, ptr); + jna.deallocate(ptr); +} + +TEST(JemallocNodumpAllocatorTest, IOBuf) { + folly::JemallocNodumpAllocator jna; + +#ifdef FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + if (folly::usingJEMalloc()) { + EXPECT_NE(0, jna.getArenaIndex()); + } +#endif // FOLLY_JEMALLOC_NODUMP_ALLOCATOR_SUPPORTED + + const size_t size{1024}; + void* ptr = jna.allocate(size); + EXPECT_NE(nullptr, ptr); + folly::IOBuf ioBuf(folly::IOBuf::TAKE_OWNERSHIP, ptr, size); + EXPECT_EQ(size, ioBuf.capacity()); + EXPECT_EQ(ptr, ioBuf.data()); + uint8_t* data = ioBuf.writableData(); + EXPECT_EQ(ptr, data); + for (auto i = 0u; i < ioBuf.capacity(); ++i) { + data[i] = 'A'; + } + uint8_t* p = static_cast (ptr); + for (auto i = 0u; i < size; ++i) { + EXPECT_EQ('A', p[i]); + } +} -- 2.34.1