diff --git a/core/include/join/bump_allocator.hpp b/core/include/join/bump_allocator.hpp index e2d4c749..4667356b 100644 --- a/core/include/join/bump_allocator.hpp +++ b/core/include/join/bump_allocator.hpp @@ -30,8 +30,11 @@ #include // C++. +#include +#include #include #include +#include // C. #include @@ -40,7 +43,7 @@ namespace join { /** - * @brief bump allocator owning a backend of fixed capacity. + * @brief bump allocator owning its backends. */ template class BasicBumpAllocator @@ -78,10 +81,14 @@ namespace join */ BasicBumpAllocator (BasicBumpAllocator&& other) noexcept : _backend (std::move (other._backend)) + , _previous (other._previous) , _base (other._base) + , _capacity (other._capacity) , _cursor (std::move (other._cursor)) { + other._previous = nullptr; other._base = nullptr; + other._capacity = Capacity; other._cursor.store (0); } @@ -92,12 +99,21 @@ namespace join */ BasicBumpAllocator& operator= (BasicBumpAllocator&& other) noexcept { - _backend = std::move (other._backend); - _base = other._base; - _cursor = std::move (other._cursor); - - other._base = nullptr; - other._cursor.store (0); + if (this != &other) + { + release (); + + _backend = std::move (other._backend); + _previous = other._previous; + _base = other._base; + _capacity = other._capacity; + _cursor = std::move (other._cursor); + + other._previous = nullptr; + other._base = nullptr; + other._capacity = Capacity; + other._cursor.store (0); + } return *this; } @@ -105,7 +121,10 @@ namespace join /** * @brief destroy instance. */ - ~BasicBumpAllocator () = default; + ~BasicBumpAllocator () + { + release (); + } /** * @brief allocate memory at the cursor, aligned on the largest power of 2 dividing size, up to max_align_t. @@ -139,8 +158,12 @@ namespace join { offset = ((base + current + alignment - 1) & ~(alignment - 1)) - base; - if (JOIN_UNLIKELY ((offset > Capacity) || (size > Capacity - offset))) + if (JOIN_UNLIKELY ((offset > _capacity) || (size > _capacity - offset))) { + if constexpr (std::is_same_v) + { + return grow (size, alignment); + } return nullptr; } } @@ -149,16 +172,26 @@ namespace join return _base + offset; } + /** + * @brief release memory (no-op). + * @param p memory to release. + * @param size allocation size (ignored). + */ + void deallocate ([[maybe_unused]] void* p, [[maybe_unused]] size_t size) noexcept + { + } + /** * @brief release all allocations at once. */ void reset () noexcept { + release (); _cursor.store (0); } /** - * @brief get the capacity. + * @brief get the initial capacity. * @return capacity in bytes. */ static constexpr size_t capacity () noexcept @@ -167,7 +200,7 @@ namespace join } /** - * @brief get the number of bytes consumed, alignment padding included. + * @brief get the number of bytes consumed in the current block, alignment padding included. * @return consumed bytes. */ size_t used () const noexcept @@ -186,7 +219,7 @@ namespace join #ifdef JOIN_HAS_NUMA /** - * @brief bind memory to a NUMA node. + * @brief bind the current block to a NUMA node. * @param numa NUMA node ID. * @return 0 on success, -1 on failure. */ @@ -197,7 +230,7 @@ namespace join #endif /** - * @brief lock memory in RAM. + * @brief lock the current block in RAM. * @return 0 on success, -1 on failure. */ int mlock () const noexcept @@ -206,12 +239,63 @@ namespace join } private: + /** + * @brief chain a new backend. + * @param size allocation size. + * @param alignment allocation alignment. + * @return pointer to the allocated memory, or nullptr on failure. + */ + void* grow (size_t size, size_t alignment) noexcept + { + if (JOIN_UNLIKELY (size + alignment + sizeof (BasicBumpAllocator) + alignof (BasicBumpAllocator) < size)) + { + return nullptr; + } + + try + { + const uint64_t capacity = + (std::max (_capacity * 2, size + alignment) + alignof (BasicBumpAllocator) - 1) & + ~(alignof (BasicBumpAllocator) - 1); + Backend backend (capacity + sizeof (BasicBumpAllocator)); + _previous = + ::new (static_cast (backend.get ()) + capacity) BasicBumpAllocator (std::move (*this)); + _backend = std::move (backend); + _base = static_cast (_backend.get ()); + _capacity = capacity; + _cursor.store (0); + } + catch (...) + { + return nullptr; + } + return allocate (size, alignment); + } + + /** + * @brief release the chained allocators. + */ + void release () noexcept + { + if (_previous != nullptr) + { + _previous->~BasicBumpAllocator (); + _previous = nullptr; + } + } + /// memory backend. Backend _backend; + /// chained allocator. + BasicBumpAllocator* _previous = nullptr; + /// start of the backend region. char* _base = nullptr; + /// backend capacity. + uint64_t _capacity = Capacity; + /// allocation cursor. SyncPolicy _cursor; }; diff --git a/core/include/join/memory.hpp b/core/include/join/memory.hpp index 60501d1c..fbb3a402 100644 --- a/core/include/join/memory.hpp +++ b/core/include/join/memory.hpp @@ -33,6 +33,7 @@ #include #include #include +#include // C. #include @@ -44,13 +45,17 @@ #include #endif #include +#include #include +#include namespace join { /// allocator forward declarations. class BumpAtomic; + class BumpPlain; + template class BasicBumpAllocator; @@ -147,13 +152,168 @@ namespace join if (::mlock (ptr, size) == -1) { + // LCOV_EXCL_START lastError = std::error_code (errno, std::generic_category ()); return -1; + // LCOV_EXCL_STOP } return 0; } + /** + * @brief heap memory provider. + */ + class HeapMem + { + public: + template + using BumpAllocator = BasicBumpAllocator; + + /** + * @brief default constructor. + */ + HeapMem () noexcept = default; + + /** + * @brief allocates a heap memory segment. + * @param size allocation size in bytes. + * @throw std::bad_alloc if size is 0, too large or malloc fails. + */ + explicit HeapMem (uint64_t size) + : _ptr (((size != 0) && (size <= static_cast (std::numeric_limits::max ()))) + ? std::malloc (size) + : nullptr) + { + if (_ptr == nullptr) + { + throw std::bad_alloc (); + } + _size = size; + } + + /** + * @brief copy constructor. + * @param other other object to copy. + */ + HeapMem (const HeapMem& other) = delete; + + /** + * @brief copy assignment operator. + * @param other other object to copy. + * @return this. + */ + HeapMem& operator= (const HeapMem& other) = delete; + + /** + * @brief move constructor. + * @param other other object to move. + */ + HeapMem (HeapMem&& other) noexcept + : _ptr (other._ptr) + , _size (other._size) + { + other._ptr = nullptr; + other._size = 0; + } + + /** + * @brief move assignment operator. + * @param other other object to move. + * @return this. + */ + HeapMem& operator= (HeapMem&& other) noexcept + { + cleanup (); + + _ptr = other._ptr; + _size = other._size; + + other._ptr = nullptr; + other._size = 0; + + return *this; + } + + /** + * @brief releases the memory. + */ + ~HeapMem () noexcept + { + cleanup (); + } + + /** + * @brief check if the memory is allocated. + * @return true if the memory is allocated, false otherwise. + */ + bool mapped () const noexcept + { + return _ptr != nullptr; + } + + /** + * @brief get a const pointer to the memory at a given offset. + * @param offset byte offset from the start of the memory. + * @return pointer to the memory at the specified offset. + * @throw std::runtime_error if memory is not allocated. + * @throw std::out_of_range if offset is out of bounds. + */ + const void* get (uint64_t offset = 0) const + { + if (JOIN_UNLIKELY (_ptr == nullptr)) + { + throw std::runtime_error ("memory not mapped"); + } + + if (JOIN_UNLIKELY (offset >= _size)) + { + throw std::out_of_range ("offset out of bounds"); + } + + return static_cast (_ptr) + offset; + } + + /** + * @brief get a pointer to the memory at a given offset. + * @param offset byte offset from the start of the memory. + * @return pointer to the memory at the specified offset. + * @throw std::runtime_error if memory is not allocated. + * @throw std::out_of_range if offset is out of bounds. + */ + void* get (uint64_t offset = 0) + { + if (JOIN_UNLIKELY (_ptr == nullptr)) + { + throw std::runtime_error ("memory not mapped"); + } + + if (JOIN_UNLIKELY (offset >= _size)) + { + throw std::out_of_range ("offset out of bounds"); + } + + return static_cast (_ptr) + offset; + } + + private: + /** + * @brief cleanup the memory. + */ + void cleanup () noexcept + { + std::free (_ptr); + _ptr = nullptr; + _size = 0; + } + + /// pointer to allocated memory. + void* _ptr = nullptr; + + /// memory size. + uint64_t _size = 0; + }; + /** * @brief local anonymous memory provider. */ @@ -329,8 +489,10 @@ namespace join MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE | MAP_HUGETLB, -1, 0); if (_ptr != MAP_FAILED) { + // LCOV_EXCL_START _size = size; return; + // LCOV_EXCL_STOP } if ((errno != ENOMEM) && (errno != EINVAL)) @@ -577,9 +739,11 @@ namespace join if (fstat (_fd, &st) == -1) { + // LCOV_EXCL_START int err = errno; ::close (_fd); throw std::system_error (err, std::generic_category (), "fstat failed"); + // LCOV_EXCL_STOP } if (static_cast (st.st_size) != _size) diff --git a/core/include/join/slab_allocator.hpp b/core/include/join/slab_allocator.hpp index ded7a22f..6cb44dac 100644 --- a/core/include/join/slab_allocator.hpp +++ b/core/include/join/slab_allocator.hpp @@ -427,6 +427,16 @@ namespace join deallocateImplem<0> (p); } + /** + * @brief return memory to the appropriate pool. + * @param p memory to return. + * @param size allocation size (ignored). + */ + void deallocate (void* p, [[maybe_unused]] size_t size) noexcept + { + deallocate (p); + } + /** * @brief check if the slab allocator still owns a memory region. * @return true if the slab allocator owns a memory region, false if it has been moved from. diff --git a/core/include/join/thread_pool.hpp b/core/include/join/thread_pool.hpp index dc6ee514..b133343e 100644 --- a/core/include/join/thread_pool.hpp +++ b/core/include/join/thread_pool.hpp @@ -33,7 +33,6 @@ // C++. #include #include -#include #include #include @@ -174,7 +173,7 @@ namespace join Mutex _mutex; /// gracefully stop all threads. - std::atomic _stop; + bool _stop = false; /// jobs queue. std::deque> _jobs; diff --git a/core/src/thread_pool.cpp b/core/src/thread_pool.cpp index a2fb9a1e..5a39a10f 100644 --- a/core/src/thread_pool.cpp +++ b/core/src/thread_pool.cpp @@ -61,9 +61,9 @@ void WorkerThread::work () { ScopedLock lock (_pool->_mutex); _pool->_condition.wait (lock, [&] () { - return _pool->_stop.load (std::memory_order_relaxed) || !_pool->_jobs.empty (); + return _pool->_stop || !_pool->_jobs.empty (); }); - if (_pool->_stop.load (std::memory_order_relaxed) && _pool->_jobs.empty ()) + if (_pool->_stop && _pool->_jobs.empty ()) { return; } @@ -79,7 +79,6 @@ void WorkerThread::work () // METHOD : ThreadPool // ========================================================================= ThreadPool::ThreadPool (int workers) -: _stop (false) { if (workers <= 0) { @@ -100,7 +99,10 @@ ThreadPool::ThreadPool (int workers) // ========================================================================= ThreadPool::~ThreadPool () noexcept { - _stop.store (true, std::memory_order_relaxed); + { + ScopedLock lock (_mutex); + _stop = true; + } _condition.broadcast (); _workers.clear (); } diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt index 2e39c8ad..af20af66 100644 --- a/core/tests/CMakeLists.txt +++ b/core/tests/CMakeLists.txt @@ -116,6 +116,16 @@ target_link_libraries(rdtsc_stats.gtest ${JOIN_CORE} GTest::gtest_main rt) add_test(NAME rdtsc_stats.gtest COMMAND rdtsc_stats.gtest) install(TARGETS rdtsc_stats.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) +add_executable(heap_mem.gtest heap_mem_test.cpp) +target_link_libraries(heap_mem.gtest ${JOIN_CORE} GTest::gtest_main rt) +add_test(NAME heap_mem.gtest COMMAND heap_mem.gtest) +install(TARGETS heap_mem.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) + +add_executable(heap_bump.gtest heap_bump_test.cpp) +target_link_libraries(heap_bump.gtest ${JOIN_CORE} GTest::gtest_main rt) +add_test(NAME heap_bump.gtest COMMAND heap_bump.gtest) +install(TARGETS heap_bump.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) + add_executable(local_mem.gtest local_mem_test.cpp) target_link_libraries(local_mem.gtest ${JOIN_CORE} GTest::gtest_main rt) add_test(NAME local_mem.gtest COMMAND local_mem.gtest) diff --git a/core/tests/heap_bump_test.cpp b/core/tests/heap_bump_test.cpp new file mode 100644 index 00000000..f4d5d438 --- /dev/null +++ b/core/tests/heap_bump_test.cpp @@ -0,0 +1,125 @@ +/** + * MIT License + * + * Copyright (c) 2026 Mathieu Rabine + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// libjoin. +#include + +// libraries. +#include + +// C++. +#include +#include + +using join::BumpPlain; +using join::HeapMem; + +/** + * @brief test move. + */ +TEST (HeapBump, move) +{ + HeapMem::BumpAllocator<64, BumpPlain> plain1; + HeapMem::BumpAllocator<64, BumpPlain> plain2; + + char* p1 = static_cast (plain1.allocate (48)); + ASSERT_NE (p1, nullptr); + std::memset (p1, 0x5A, 48); + ASSERT_NE (plain1.allocate (48), nullptr); + ASSERT_NE (plain2.allocate (48), nullptr); + ASSERT_NE (plain2.allocate (48), nullptr); + + plain2 = std::move (plain1); + + ASSERT_FALSE (plain1.hasBackend ()); + ASSERT_EQ (plain1.allocate (1), nullptr); + ASSERT_TRUE (plain2.hasBackend ()); + ASSERT_EQ (p1[47], 0x5A); + + HeapMem::BumpAllocator<64, BumpPlain> plain3 (std::move (plain2)); + + ASSERT_FALSE (plain2.hasBackend ()); + ASSERT_TRUE (plain3.hasBackend ()); + ASSERT_EQ (p1[0], 0x5A); +} + +/** + * @brief test allocate. + */ +TEST (HeapBump, allocate) +{ + HeapMem::BumpAllocator<64> allocator; + + ASSERT_NE (allocator.allocate (64), nullptr); + ASSERT_EQ (allocator.allocate (1), nullptr); + + HeapMem::BumpAllocator<64, BumpPlain> plain; + + std::vector blocks; + for (int i = 0; i < 64; ++i) + { + char* p = static_cast (plain.allocate (100)); + ASSERT_NE (p, nullptr); + std::memset (p, i, 100); + blocks.push_back (p); + } + + for (size_t i = 0; i < blocks.size (); ++i) + { + ASSERT_EQ (blocks[i][0], static_cast (i)); + ASSERT_EQ (blocks[i][99], static_cast (i)); + } + + char* aligned = static_cast (plain.allocate (1, 4096)); + ASSERT_NE (aligned, nullptr); + ASSERT_EQ (reinterpret_cast (aligned) % 4096, 0); + + ASSERT_EQ (plain.allocate (SIZE_MAX), nullptr); + ASSERT_EQ (plain.allocate (SIZE_MAX / 2), nullptr); +} + +/** + * @brief test reset. + */ +TEST (HeapBump, reset) +{ + HeapMem::BumpAllocator<64, BumpPlain> plain; + + ASSERT_NE (plain.allocate (64), nullptr); + void* p1 = plain.allocate (1); + ASSERT_NE (p1, nullptr); + + plain.reset (); + ASSERT_EQ (plain.used (), 0); + ASSERT_EQ (plain.allocate (1), p1); +} + +/** + * @brief main function. + */ +int main (int argc, char** argv) +{ + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +} diff --git a/core/tests/heap_mem_test.cpp b/core/tests/heap_mem_test.cpp new file mode 100644 index 00000000..e3427401 --- /dev/null +++ b/core/tests/heap_mem_test.cpp @@ -0,0 +1,98 @@ +/** + * MIT License + * + * Copyright (c) 2026 Mathieu Rabine + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// libjoin. +#include + +// libraries. +#include + +using join::HeapMem; + +/** + * @brief test create. + */ +TEST (HeapMem, create) +{ + ASSERT_THROW (HeapMem (0), std::bad_alloc); + ASSERT_THROW (HeapMem (UINT64_MAX), std::bad_alloc); + + HeapMem mem1 (join::pageSize ()); + ASSERT_NE (mem1.get (), nullptr); + HeapMem mem2 (std::move (mem1)); + ASSERT_THROW (mem1.get (), std::runtime_error); + ASSERT_NE (mem2.get (), nullptr); + + HeapMem mem3 (4 * join::pageSize () + 1); + ASSERT_NE (mem3.get (), nullptr); +} + +/** + * @brief test empty. + */ +TEST (HeapMem, empty) +{ + HeapMem mem1; + const HeapMem& cmem1 = mem1; + + ASSERT_FALSE (mem1.mapped ()); + ASSERT_THROW (mem1.get (), std::runtime_error); + ASSERT_THROW (cmem1.get (), std::runtime_error); + + mem1 = HeapMem (join::pageSize ()); + ASSERT_TRUE (mem1.mapped ()); + ASSERT_NE (mem1.get (), nullptr); + ASSERT_NE (cmem1.get (), nullptr); +} + +/** + * @brief test get. + */ +TEST (HeapMem, get) +{ + HeapMem mem1 (join::pageSize ()); + const HeapMem& cmem1 = mem1; + + EXPECT_THROW (mem1.get (join::pageSize ()), std::out_of_range); + EXPECT_THROW (cmem1.get (join::pageSize ()), std::out_of_range); + + ASSERT_EQ (static_cast (mem1.get (1)), static_cast (mem1.get ()) + 1); + ASSERT_EQ (static_cast (cmem1.get (1)), static_cast (cmem1.get ()) + 1); + + HeapMem mem2 (join::pageSize ()); + mem2 = std::move (mem1); + + EXPECT_THROW (mem1.get (), std::runtime_error); + EXPECT_THROW (cmem1.get (), std::runtime_error); + ASSERT_NE (mem2.get (), nullptr); +} + +/** + * @brief main function. + */ +int main (int argc, char** argv) +{ + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +} diff --git a/core/tests/local_bump_test.cpp b/core/tests/local_bump_test.cpp index e76f856c..2ba11405 100644 --- a/core/tests/local_bump_test.cpp +++ b/core/tests/local_bump_test.cpp @@ -81,7 +81,14 @@ TEST (LocalBump, move) ASSERT_TRUE (plain3.hasBackend ()); ASSERT_EQ (plain3.used (), 32); ASSERT_NE (plain3.allocate (32), nullptr); - ASSERT_EQ (plain3.allocate (1), nullptr); + ASSERT_NE (plain3.allocate (1), nullptr); + ASSERT_EQ (plain3.used (), 1); + + LocalMem::BumpAllocator<64, BumpPlain>& self = plain3; + plain3 = std::move (self); + + ASSERT_TRUE (plain3.hasBackend ()); + ASSERT_EQ (plain3.used (), 1); } /** @@ -145,7 +152,33 @@ TEST (LocalBump, allocate) ASSERT_EQ (u3 - u1, 64); ASSERT_NE (plain.allocate (176), nullptr); - ASSERT_EQ (plain.allocate (1), nullptr); + ASSERT_NE (plain.allocate (1), nullptr); + ASSERT_EQ (plain.used (), 1); + + ASSERT_NE (plain.allocate (4096), nullptr); + ASSERT_EQ (plain.used (), 4096); + + ASSERT_EQ (plain.allocate (SIZE_MAX / 2), nullptr); +} + +/** + * @brief test deallocate. + */ +TEST (LocalBump, deallocate) +{ + LocalMem::BumpAllocator<64> allocator; + + void* p1 = allocator.allocate (32); + ASSERT_NE (p1, nullptr); + allocator.deallocate (p1, 32); + ASSERT_EQ (allocator.used (), 32); + + LocalMem::BumpAllocator<64, BumpPlain> plain; + + void* u1 = plain.allocate (32); + ASSERT_NE (u1, nullptr); + plain.deallocate (u1, 32); + ASSERT_EQ (plain.used (), 32); } /** @@ -165,13 +198,13 @@ TEST (LocalBump, reset) LocalMem::BumpAllocator<64, BumpPlain> plain; - void* u1 = plain.allocate (64); + ASSERT_NE (plain.allocate (64), nullptr); + void* u1 = plain.allocate (1); ASSERT_NE (u1, nullptr); - ASSERT_EQ (plain.allocate (1), nullptr); plain.reset (); ASSERT_EQ (plain.used (), 0); - ASSERT_EQ (plain.allocate (64), u1); + ASSERT_EQ (plain.allocate (1), u1); } /** diff --git a/core/tests/local_slab_test.cpp b/core/tests/local_slab_test.cpp index 3d65ff71..cfe43588 100644 --- a/core/tests/local_slab_test.cpp +++ b/core/tests/local_slab_test.cpp @@ -142,7 +142,7 @@ TEST (LocalSlab, deallocate) ASSERT_NO_THROW (allocator.deallocate (nullptr)); ASSERT_NO_THROW (allocator.deallocate (p2)); - ASSERT_NO_THROW (allocator.deallocate (p1)); + ASSERT_NO_THROW (allocator.deallocate (p1, 64)); int dummy; void* dummyPtr = &dummy; diff --git a/core/tests/shm_mem_test.cpp b/core/tests/shm_mem_test.cpp index 10cf771c..87abe899 100644 --- a/core/tests/shm_mem_test.cpp +++ b/core/tests/shm_mem_test.cpp @@ -29,8 +29,10 @@ #include // C. +#include #include #include +#include #include #include @@ -84,6 +86,17 @@ TEST_F (PosixMem, create) ASSERT_THROW (mem1.get (), std::runtime_error); ASSERT_NE (mem2.get (), nullptr); ASSERT_THROW (ShmMem (2 * page, _name), std::runtime_error); + + ASSERT_EQ (ShmMem::unlink (_name), 0) << join::lastError.message (); + struct rlimit limit; + ASSERT_EQ (::getrlimit (RLIMIT_FSIZE, &limit), 0) << strerror (errno); + struct rlimit small = limit; + small.rlim_cur = page; + sighandler_t handler = ::signal (SIGXFSZ, SIG_IGN); + ASSERT_EQ (::setrlimit (RLIMIT_FSIZE, &small), 0) << strerror (errno); + EXPECT_THROW (ShmMem (2 * page, _name), std::system_error); + ASSERT_EQ (::setrlimit (RLIMIT_FSIZE, &limit), 0) << strerror (errno); + ::signal (SIGXFSZ, handler); } TEST_F (PosixMem, get)