Add exception handling for new operator (#791)
The new operator in the snmalloc did not throw exceptions in the case of allocation failure. Moreover, it was not possible to override the behaviour of the failure of the new operator using the std::set_new_handler function. This PR adds the necessary code to the snmalloc new operator to throw std::bad_alloc when the allocation fails. It also allows the std::set_new_handler function to be used to set a custom handler for allocation failures.
This commit is contained in:
committed by
GitHub
parent
b49a42d9be
commit
452fcc44b0
@@ -50,7 +50,7 @@ namespace snmalloc
|
||||
return p;
|
||||
}
|
||||
|
||||
static void* failure(size_t size)
|
||||
static void* failure(size_t size) noexcept
|
||||
{
|
||||
UNUSED(size);
|
||||
// If we are here, then the allocation failed.
|
||||
@@ -401,9 +401,9 @@ namespace snmalloc
|
||||
* the stack as often closing over the arguments would cause less good
|
||||
* codegen.
|
||||
*/
|
||||
template<typename Action, typename... Args>
|
||||
template<bool noexc, typename Action, typename... Args>
|
||||
SNMALLOC_FAST_PATH decltype(auto)
|
||||
handle_message_queue(Action action, Args... args)
|
||||
handle_message_queue(Action action, Args... args) noexcept(noexc)
|
||||
{
|
||||
// Inline the empty check, but not necessarily the full queue handling.
|
||||
if (SNMALLOC_LIKELY(!has_messages()))
|
||||
@@ -411,15 +411,15 @@ namespace snmalloc
|
||||
return action(args...);
|
||||
}
|
||||
|
||||
return handle_message_queue_slow(action, args...);
|
||||
return handle_message_queue_slow<noexc>(action, args...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process remote frees into this allocator.
|
||||
*/
|
||||
template<typename Action, typename... Args>
|
||||
template<bool noexc, typename Action, typename... Args>
|
||||
SNMALLOC_SLOW_PATH decltype(auto)
|
||||
handle_message_queue_slow(Action action, Args... args)
|
||||
handle_message_queue_slow(Action action, Args... args) noexcept(noexc)
|
||||
{
|
||||
bool need_post = false;
|
||||
size_t bytes_freed = 0;
|
||||
@@ -602,7 +602,8 @@ namespace snmalloc
|
||||
* required. It is defaulted to do nothing.
|
||||
*/
|
||||
template<typename Conts = Uninit, typename CheckInit = CheckInitNoOp>
|
||||
SNMALLOC_FAST_PATH ALLOCATOR void* alloc(size_t size)
|
||||
SNMALLOC_FAST_PATH ALLOCATOR void*
|
||||
alloc(size_t size) noexcept(noexcept(Conts::failure(0)))
|
||||
{
|
||||
// Perform the - 1 on size, so that zero wraps around and ends up on
|
||||
// slow path.
|
||||
@@ -621,7 +622,8 @@ namespace snmalloc
|
||||
* Fast allocation for small objects.
|
||||
*/
|
||||
template<typename Conts, typename CheckInit>
|
||||
SNMALLOC_FAST_PATH void* small_alloc(size_t size)
|
||||
SNMALLOC_FAST_PATH void*
|
||||
small_alloc(size_t size) noexcept(noexcept(Conts::failure(0)))
|
||||
{
|
||||
auto domesticate =
|
||||
[this](freelist::QueuePtr p) SNMALLOC_FAST_PATH_LAMBDA {
|
||||
@@ -637,7 +639,7 @@ namespace snmalloc
|
||||
return finish_alloc<Conts>(p, size);
|
||||
}
|
||||
|
||||
return handle_message_queue(
|
||||
return handle_message_queue<noexcept(Conts::failure(0))>(
|
||||
[](
|
||||
Allocator* alloc,
|
||||
smallsizeclass_t sizeclass,
|
||||
@@ -661,8 +663,8 @@ namespace snmalloc
|
||||
* register.
|
||||
*/
|
||||
template<typename Conts, typename CheckInit>
|
||||
static SNMALLOC_SLOW_PATH void*
|
||||
alloc_not_small(size_t size, Allocator* self)
|
||||
static SNMALLOC_SLOW_PATH void* alloc_not_small(
|
||||
size_t size, Allocator* self) noexcept(noexcept(Conts::failure(0)))
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
@@ -672,7 +674,7 @@ namespace snmalloc
|
||||
return self->small_alloc<Conts, CheckInit>(1);
|
||||
}
|
||||
|
||||
return self->handle_message_queue(
|
||||
return self->template handle_message_queue<noexcept(Conts::failure(0))>(
|
||||
[](Allocator* self, size_t size) SNMALLOC_FAST_PATH_LAMBDA {
|
||||
return CheckInit::check_init(
|
||||
[self, size]() SNMALLOC_FAST_PATH_LAMBDA {
|
||||
@@ -739,7 +741,9 @@ namespace snmalloc
|
||||
|
||||
template<typename Conts, typename CheckInit>
|
||||
SNMALLOC_FAST_PATH void* small_refill(
|
||||
smallsizeclass_t sizeclass, freelist::Iter<>& fast_free_list, size_t size)
|
||||
smallsizeclass_t sizeclass,
|
||||
freelist::Iter<>& fast_free_list,
|
||||
size_t size) noexcept(noexcept(Conts::failure(0)))
|
||||
{
|
||||
void* result = Config::SecondaryAllocator::allocate(
|
||||
[size]() -> stl::Pair<size_t, size_t> {
|
||||
@@ -809,7 +813,9 @@ namespace snmalloc
|
||||
|
||||
template<typename Conts, typename CheckInit>
|
||||
SNMALLOC_SLOW_PATH void* small_refill_slow(
|
||||
smallsizeclass_t sizeclass, freelist::Iter<>& fast_free_list, size_t size)
|
||||
smallsizeclass_t sizeclass,
|
||||
freelist::Iter<>& fast_free_list,
|
||||
size_t size) noexcept(noexcept(Conts::failure(0)))
|
||||
{
|
||||
return CheckInit::check_init(
|
||||
[this, size, sizeclass, &fast_free_list]() SNMALLOC_FAST_PATH_LAMBDA {
|
||||
@@ -994,7 +1000,7 @@ namespace snmalloc
|
||||
* deallocation to the other allocators.
|
||||
***************************************************************************/
|
||||
template<typename CheckInit = CheckInitNoOp>
|
||||
SNMALLOC_FAST_PATH void dealloc(void* p_raw)
|
||||
SNMALLOC_FAST_PATH void dealloc(void* p_raw) noexcept
|
||||
{
|
||||
#ifdef __CHERI_PURE_CAPABILITY__
|
||||
/*
|
||||
@@ -1043,7 +1049,7 @@ namespace snmalloc
|
||||
|
||||
SNMALLOC_FAST_PATH void dealloc_local_object(
|
||||
CapPtr<void, capptr::bounds::Alloc> p,
|
||||
const typename Config::PagemapEntry& entry)
|
||||
const typename Config::PagemapEntry& entry) noexcept
|
||||
{
|
||||
auto meta = entry.get_slab_metadata();
|
||||
|
||||
@@ -1078,7 +1084,7 @@ namespace snmalloc
|
||||
SNMALLOC_SLOW_PATH void dealloc_local_object_slow(
|
||||
capptr::Alloc<void> p,
|
||||
const PagemapEntry& entry,
|
||||
BackendSlabMetadata* meta)
|
||||
BackendSlabMetadata* meta) noexcept
|
||||
{
|
||||
// TODO: Handle message queue on this path?
|
||||
|
||||
@@ -1273,8 +1279,8 @@ namespace snmalloc
|
||||
}
|
||||
|
||||
template<typename CheckInit>
|
||||
SNMALLOC_FAST_PATH void
|
||||
dealloc_remote(const PagemapEntry& entry, capptr::Alloc<void> p_tame)
|
||||
SNMALLOC_FAST_PATH void dealloc_remote(
|
||||
const PagemapEntry& entry, capptr::Alloc<void> p_tame) noexcept
|
||||
{
|
||||
if (SNMALLOC_LIKELY(entry.is_owned()))
|
||||
{
|
||||
@@ -1327,8 +1333,8 @@ namespace snmalloc
|
||||
* as we might acquire the originating allocator.
|
||||
*/
|
||||
template<typename CheckInit>
|
||||
SNMALLOC_SLOW_PATH void
|
||||
dealloc_remote_slow(const PagemapEntry& entry, capptr::Alloc<void> p)
|
||||
SNMALLOC_SLOW_PATH void dealloc_remote_slow(
|
||||
const PagemapEntry& entry, capptr::Alloc<void> p) noexcept
|
||||
{
|
||||
CheckInit::check_init(
|
||||
[this, &entry, p]() SNMALLOC_FAST_PATH_LAMBDA {
|
||||
@@ -1344,7 +1350,7 @@ namespace snmalloc
|
||||
|
||||
post();
|
||||
},
|
||||
[](Allocator* a, void* p) {
|
||||
[](Allocator* a, void* p) SNMALLOC_FAST_PATH_LAMBDA {
|
||||
// Recheck what kind of dealloc we should do in case the allocator
|
||||
// we get from lazy_init is the originating allocator.
|
||||
a->dealloc(p); // TODO don't double count statistics
|
||||
@@ -1385,7 +1391,7 @@ namespace snmalloc
|
||||
// Process incoming message queue
|
||||
// Loop as normally only processes a batch
|
||||
while (has_messages())
|
||||
handle_message_queue([]() {});
|
||||
handle_message_queue<true>([]() {});
|
||||
}
|
||||
|
||||
auto& key = freelist::Object::key_root;
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace snmalloc
|
||||
* how many calls for the next time we hit the slow path.
|
||||
*/
|
||||
template<typename T = void*>
|
||||
SNMALLOC_SLOW_PATH T check_tick_slow(T p = nullptr)
|
||||
SNMALLOC_SLOW_PATH T check_tick_slow(T p = nullptr) noexcept
|
||||
{
|
||||
uint64_t now_ms = PAL::time_in_ms();
|
||||
|
||||
|
||||
@@ -20,24 +20,100 @@
|
||||
|
||||
// only define these if we are not using the vendored STL
|
||||
#ifndef SNMALLOC_USE_SELF_VENDORED_STL
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
namespace handler
|
||||
{
|
||||
class Base
|
||||
{
|
||||
public:
|
||||
static void*
|
||||
success(void* p, size_t size, bool secondary_allocator = false)
|
||||
{
|
||||
UNUSED(secondary_allocator, size);
|
||||
SNMALLOC_ASSERT(p != nullptr);
|
||||
|
||||
SNMALLOC_ASSERT(
|
||||
secondary_allocator ||
|
||||
is_start_of_object(size_to_sizeclass_full(size), address_cast(p)));
|
||||
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
class Throw : public Base
|
||||
{
|
||||
public:
|
||||
static void* failure(size_t size)
|
||||
{
|
||||
// Throw std::bad_alloc on failure.
|
||||
auto new_handler = std::get_new_handler();
|
||||
if (new_handler != nullptr)
|
||||
{
|
||||
// Call the new handler, which may throw an exception.
|
||||
new_handler();
|
||||
// Retry now new_handler has been called.
|
||||
// I dislike the unbounded retrying here, but that seems to be what
|
||||
// other implementations do.
|
||||
return alloc<Throw>(size);
|
||||
}
|
||||
|
||||
// Throw std::bad_alloc on failure.
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
};
|
||||
|
||||
class NoThrow : public Base
|
||||
{
|
||||
public:
|
||||
static void* failure(size_t size) noexcept
|
||||
{
|
||||
auto new_handler = std::get_new_handler();
|
||||
if (new_handler != nullptr)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Call the new handler, which may throw an exception.
|
||||
new_handler();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If the new handler throws, we just return nullptr.
|
||||
return nullptr;
|
||||
}
|
||||
// Retry now new_handler has been called.
|
||||
return alloc<NoThrow>(size);
|
||||
}
|
||||
// If we are here, then the allocation failed.
|
||||
// Set errno to ENOMEM, as per the C standard.
|
||||
errno = ENOMEM;
|
||||
|
||||
// Return nullptr on failure.
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
} // namespace snmalloc
|
||||
|
||||
void* operator new(size_t size)
|
||||
{
|
||||
return snmalloc::libc::malloc(size);
|
||||
return snmalloc::alloc<snmalloc::handler::Throw>(size);
|
||||
}
|
||||
|
||||
void* operator new[](size_t size)
|
||||
{
|
||||
return snmalloc::libc::malloc(size);
|
||||
return snmalloc::alloc<snmalloc::handler::Throw>(size);
|
||||
}
|
||||
|
||||
void* operator new(size_t size, std::nothrow_t&)
|
||||
void* operator new(size_t size, const std::nothrow_t&) noexcept
|
||||
{
|
||||
return snmalloc::libc::malloc(size);
|
||||
return snmalloc::alloc<snmalloc::handler::NoThrow>(size);
|
||||
}
|
||||
|
||||
void* operator new[](size_t size, std::nothrow_t&)
|
||||
void* operator new[](size_t size, const std::nothrow_t&) noexcept
|
||||
{
|
||||
return snmalloc::libc::malloc(size);
|
||||
return snmalloc::alloc<snmalloc::handler::NoThrow>(size);
|
||||
}
|
||||
|
||||
void operator delete(void* p) EXCEPTSPEC
|
||||
@@ -50,7 +126,7 @@ void operator delete(void* p, size_t size) EXCEPTSPEC
|
||||
snmalloc::libc::free_sized(p, size);
|
||||
}
|
||||
|
||||
void operator delete(void* p, std::nothrow_t&)
|
||||
void operator delete(void* p, const std::nothrow_t&) noexcept
|
||||
{
|
||||
snmalloc::libc::free(p);
|
||||
}
|
||||
@@ -65,7 +141,7 @@ void operator delete[](void* p, size_t size) EXCEPTSPEC
|
||||
snmalloc::libc::free_sized(p, size);
|
||||
}
|
||||
|
||||
void operator delete[](void* p, std::nothrow_t&)
|
||||
void operator delete[](void* p, const std::nothrow_t&) noexcept
|
||||
{
|
||||
snmalloc::libc::free(p);
|
||||
}
|
||||
@@ -73,25 +149,27 @@ void operator delete[](void* p, std::nothrow_t&)
|
||||
void* operator new(size_t size, std::align_val_t val)
|
||||
{
|
||||
size = snmalloc::aligned_size(size_t(val), size);
|
||||
return snmalloc::libc::malloc(size);
|
||||
return snmalloc::alloc<snmalloc::handler::Throw>(size);
|
||||
}
|
||||
|
||||
void* operator new[](size_t size, std::align_val_t val)
|
||||
{
|
||||
size = snmalloc::aligned_size(size_t(val), size);
|
||||
return snmalloc::libc::malloc(size);
|
||||
return snmalloc::alloc<snmalloc::handler::Throw>(size);
|
||||
}
|
||||
|
||||
void* operator new(size_t size, std::align_val_t val, std::nothrow_t&)
|
||||
void* operator new(
|
||||
size_t size, std::align_val_t val, const std::nothrow_t&) noexcept
|
||||
{
|
||||
size = snmalloc::aligned_size(size_t(val), size);
|
||||
return snmalloc::libc::malloc(size);
|
||||
return snmalloc::alloc<snmalloc::handler::NoThrow>(size);
|
||||
}
|
||||
|
||||
void* operator new[](size_t size, std::align_val_t val, std::nothrow_t&)
|
||||
void* operator new[](
|
||||
size_t size, std::align_val_t val, const std::nothrow_t&) noexcept
|
||||
{
|
||||
size = snmalloc::aligned_size(size_t(val), size);
|
||||
return snmalloc::libc::malloc(size);
|
||||
return snmalloc::alloc<snmalloc::handler::NoThrow>(size);
|
||||
}
|
||||
|
||||
void operator delete(void* p, std::align_val_t) EXCEPTSPEC
|
||||
|
||||
@@ -18,9 +18,27 @@ int main()
|
||||
# include <atomic>
|
||||
# include <iostream>
|
||||
# include <memory>
|
||||
# include <new>
|
||||
# include <snmalloc/backend/globalconfig.h>
|
||||
# include <snmalloc/snmalloc_core.h>
|
||||
|
||||
// This code makes the delete overloads build with the correct noexcept spec.
|
||||
# ifdef _WIN32
|
||||
# ifdef __clang__
|
||||
# define EXCEPTSPEC noexcept
|
||||
# else
|
||||
# define EXCEPTSPEC
|
||||
# endif
|
||||
# else
|
||||
# ifdef _GLIBCXX_USE_NOEXCEPT
|
||||
# define EXCEPTSPEC _GLIBCXX_USE_NOEXCEPT
|
||||
# elif defined(_NOEXCEPT)
|
||||
# define EXCEPTSPEC _NOEXCEPT
|
||||
# else
|
||||
# define EXCEPTSPEC
|
||||
# endif
|
||||
# endif
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
// Instantiate the allocator with a client meta data provider that uses an
|
||||
@@ -170,12 +188,12 @@ void* operator new(size_t size)
|
||||
return snmalloc::miracle::malloc(size);
|
||||
}
|
||||
|
||||
void operator delete(void* p)
|
||||
void operator delete(void* p) EXCEPTSPEC
|
||||
{
|
||||
snmalloc::miracle::free(p);
|
||||
}
|
||||
|
||||
void operator delete(void* p, size_t)
|
||||
void operator delete(void* p, size_t) EXCEPTSPEC
|
||||
{
|
||||
snmalloc::miracle::free(p);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user