249 lines
7.5 KiB
C++
249 lines
7.5 KiB
C++
/*
|
|
* GlobalNewDelete.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2026 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
// Process-wide replacements for the global operator new / operator delete set,
|
|
// owned by the fdbserver binary.
|
|
//
|
|
// These live here, in a translation unit compiled directly into the fdbserver
|
|
// executable, rather than in the `flow` static library, for two reasons:
|
|
//
|
|
// 1. Correctness of interposition. operator new / operator delete are
|
|
// replaceable functions; a definition sitting in a static archive is only
|
|
// pulled into the link if the linker already needs some other symbol from
|
|
// that same object file. Placing them in an executable TU guarantees the
|
|
// replacements are part of the final link instead of relying on incidental
|
|
// archive pull-in.
|
|
//
|
|
// 2. Client isolation. `flow` is linked into libfdb_c and every client
|
|
// binding; a global-new override compiled into it would interpose the
|
|
// entire host process's allocator in any application that loads the client.
|
|
// fdbserver is a standalone executable that clients never link, so keeping
|
|
// these here confines the interposition to the server.
|
|
//
|
|
// Exactly one implementation is compiled, chosen by the same ALLOC_INSTRUMENTATION
|
|
// flags the legacy accounting framework uses (so the two never define the global
|
|
// operators twice):
|
|
//
|
|
// - ALLOC_INSTRUMENTATION[_STDOUT] on -> legacy FastAlloc accounting hooks.
|
|
// - otherwise -> the sampled per-call-site memory
|
|
// tracker (flow/MemoryTracker.*).
|
|
|
|
#include <cstdlib>
|
|
#include <new>
|
|
|
|
#include "flow/MemoryTracker.h" // for FDB_MEMORY_TRACKER (default on)
|
|
|
|
// TODO: the old ALLOC_INSTRUMENTATION doesn't seem to be usable at
|
|
// scale. Consider deleting it.
|
|
#if defined(ALLOC_INSTRUMENTATION) || defined(ALLOC_INSTRUMENTATION_STDOUT)
|
|
|
|
#include "flow/FastAlloc.h"
|
|
|
|
void* operator new(std::size_t size) {
|
|
void* p = malloc(size);
|
|
if (!p) {
|
|
throw std::bad_alloc();
|
|
}
|
|
recordAllocation(p, size);
|
|
return p;
|
|
}
|
|
void operator delete(void* ptr) throw() {
|
|
recordDeallocation(ptr);
|
|
free(ptr);
|
|
}
|
|
|
|
void* operator new(std::size_t size, const std::nothrow_t&) throw() {
|
|
void* p = malloc(size);
|
|
recordAllocation(p, size);
|
|
return p;
|
|
}
|
|
void operator delete(void* ptr, const std::nothrow_t&) throw() {
|
|
recordDeallocation(ptr);
|
|
free(ptr);
|
|
}
|
|
|
|
void* operator new[](std::size_t size) {
|
|
void* p = malloc(size);
|
|
if (!p) {
|
|
throw std::bad_alloc();
|
|
}
|
|
recordAllocation(p, size);
|
|
return p;
|
|
}
|
|
void operator delete[](void* ptr) throw() {
|
|
recordDeallocation(ptr);
|
|
free(ptr);
|
|
}
|
|
|
|
void* operator new[](std::size_t size, const std::nothrow_t&) throw() {
|
|
void* p = malloc(size);
|
|
recordAllocation(p, size);
|
|
return p;
|
|
}
|
|
void operator delete[](void* ptr, const std::nothrow_t&) throw() {
|
|
recordDeallocation(ptr);
|
|
free(ptr);
|
|
}
|
|
|
|
#else // sampled memory tracker, see design/memory-tracker.md
|
|
|
|
#include "flow/Platform.h" // aligned_alloc / aligned_free (portable across MSVC/POSIX)
|
|
|
|
#if FDB_MEMORY_TRACKER
|
|
|
|
// NOTE: We (Apple) do not maintain a local facility to build FDB with MSVC on
|
|
// Windows, and CI only *configures* (not compiles) there — so the MSVC-specific
|
|
// pieces below are best-effort and not compile-verified: the exact signatures of
|
|
// the replaceable global operator new/delete set, and the
|
|
// aligned_alloc/aligned_free ↔ _aligned_malloc/_aligned_free pairing routed
|
|
// through flow/Platform.h. This code may have issues on MSVC; community help for
|
|
// the Windows build would be welcome. (See flow/MemoryTracker.cpp for the parallel
|
|
// note on the non-Linux frame walker.)
|
|
|
|
namespace {
|
|
|
|
// Retry through the installed std::new_handler on failure, as the default
|
|
// operator new does. fdbserver installs platform::outOfMemory, so an allocation
|
|
// failure (including the tracker's own map growth) reaches FDB's OOM diagnostics
|
|
// and FDB_EXIT_NO_MEM rather than throwing straight past them.
|
|
void* mallocWithNewHandler(std::size_t n) {
|
|
void* p;
|
|
while (!(p = std::malloc(n))) {
|
|
std::new_handler h = std::get_new_handler();
|
|
if (!h) {
|
|
throw std::bad_alloc();
|
|
}
|
|
h();
|
|
}
|
|
return p;
|
|
}
|
|
|
|
// Same handler loop for over-aligned allocations. C11 aligned_alloc requires the
|
|
// size to be a multiple of the alignment, so round up (harmless over-allocation)
|
|
// to accept arbitrary operator-new sizes.
|
|
void* alignedAllocWithNewHandler(std::size_t alignment, std::size_t n) {
|
|
std::size_t rounded = (n + alignment - 1) & ~(alignment - 1);
|
|
if (rounded < n) {
|
|
throw std::bad_alloc(); // round-up overflowed; the request can't be satisfied
|
|
}
|
|
void* p;
|
|
while (!(p = aligned_alloc(alignment, rounded))) {
|
|
std::new_handler h = std::get_new_handler();
|
|
if (!h) {
|
|
throw std::bad_alloc();
|
|
}
|
|
h();
|
|
}
|
|
return p;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void* operator new(std::size_t n) {
|
|
void* p = mallocWithNewHandler(n);
|
|
memTrackerOnAlloc(p, n);
|
|
return p;
|
|
}
|
|
void operator delete(void* p) noexcept {
|
|
memTrackerOnFree(p);
|
|
std::free(p);
|
|
}
|
|
void operator delete(void* p, std::size_t) noexcept {
|
|
memTrackerOnFree(p);
|
|
std::free(p);
|
|
}
|
|
|
|
void* operator new[](std::size_t n) {
|
|
void* p = mallocWithNewHandler(n);
|
|
memTrackerOnAlloc(p, n);
|
|
return p;
|
|
}
|
|
void operator delete[](void* p) noexcept {
|
|
memTrackerOnFree(p);
|
|
std::free(p);
|
|
}
|
|
void operator delete[](void* p, std::size_t) noexcept {
|
|
memTrackerOnFree(p);
|
|
std::free(p);
|
|
}
|
|
|
|
void* operator new(std::size_t n, const std::nothrow_t&) noexcept {
|
|
try {
|
|
void* p = mallocWithNewHandler(n);
|
|
memTrackerOnAlloc(p, n);
|
|
return p;
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
void operator delete(void* p, const std::nothrow_t&) noexcept {
|
|
memTrackerOnFree(p);
|
|
std::free(p);
|
|
}
|
|
|
|
void* operator new[](std::size_t n, const std::nothrow_t&) noexcept {
|
|
try {
|
|
void* p = mallocWithNewHandler(n);
|
|
memTrackerOnAlloc(p, n);
|
|
return p;
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
void operator delete[](void* p, const std::nothrow_t&) noexcept {
|
|
memTrackerOnFree(p);
|
|
std::free(p);
|
|
}
|
|
|
|
// C++17 over-aligned new/delete. aligned_alloc/aligned_free (flow/Platform.h)
|
|
// keep the alloc and free sides paired on MSVC (_aligned_malloc/_aligned_free).
|
|
void* operator new(std::size_t n, std::align_val_t a) {
|
|
void* p = alignedAllocWithNewHandler(static_cast<std::size_t>(a), n);
|
|
memTrackerOnAlloc(p, n);
|
|
return p;
|
|
}
|
|
void operator delete(void* p, std::align_val_t) noexcept {
|
|
memTrackerOnFree(p);
|
|
aligned_free(p);
|
|
}
|
|
void operator delete(void* p, std::size_t, std::align_val_t) noexcept {
|
|
memTrackerOnFree(p);
|
|
aligned_free(p);
|
|
}
|
|
|
|
void* operator new[](std::size_t n, std::align_val_t a) {
|
|
void* p = alignedAllocWithNewHandler(static_cast<std::size_t>(a), n);
|
|
memTrackerOnAlloc(p, n);
|
|
return p;
|
|
}
|
|
void operator delete[](void* p, std::align_val_t) noexcept {
|
|
memTrackerOnFree(p);
|
|
aligned_free(p);
|
|
}
|
|
void operator delete[](void* p, std::size_t, std::align_val_t) noexcept {
|
|
memTrackerOnFree(p);
|
|
aligned_free(p);
|
|
}
|
|
|
|
#else // !FDB_MEMORY_TRACKER — no global operator new/delete override; libc++'s is used.
|
|
#endif // FDB_MEMORY_TRACKER
|
|
|
|
#endif // ALLOC_INSTRUMENTATION
|