Skip to content

Commit

Permalink
gh-610 Added MemoryLimiter and other classes etc
Browse files Browse the repository at this point in the history
  • Loading branch information
nolan-veed authored and gavv committed Feb 13, 2024
1 parent 4015b01 commit 68a5da3
Show file tree
Hide file tree
Showing 22 changed files with 658 additions and 20 deletions.
26 changes: 26 additions & 0 deletions src/internal_modules/roc_core/heap_arena.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,31 @@ void HeapArena::deallocate(void* ptr) {
free(chunk);
}

size_t HeapArena::compute_allocated_size(size_t size) const {
return sizeof(ChunkHeader) + sizeof(ChunkCanary) + size + sizeof(ChunkCanary);
}

size_t HeapArena::allocated_size(void* ptr) const {
if (!ptr) {
roc_panic("heap arena: null pointer");
}

ChunkHeader* chunk =
ROC_CONTAINER_OF((char*)ptr - sizeof(ChunkCanary), ChunkHeader, data);

const bool is_owner = chunk->owner == this;

if (!is_owner) {
if (AtomicOps::load_seq_cst(flags_) & HeapArenaFlag_EnableGuards) {
roc_panic("heap arena: attempt to get allocated size of chunk not belonging "
"to this arena: this_arena=%p chunk_arena=%p",
(const void*)this, (const void*)chunk->owner);
}
return 0;
}

return compute_allocated_size(chunk->size);
}

} // namespace core
} // namespace roc
11 changes: 10 additions & 1 deletion src/internal_modules/roc_core/heap_arena.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,16 @@ class HeapArena : public IArena, public NonCopyable<> {
virtual void* allocate(size_t size);

//! Deallocate previously allocated memory.
virtual void deallocate(void*);
virtual void deallocate(void* ptr);

//! Computes how many bytes will be actually allocated if allocate() is called with
//! given size. Covers all internal overhead, if any.
virtual size_t compute_allocated_size(size_t size) const;

//! Returns how many bytes was allocated for given pointer returned by allocate().
//! Covers all internal overhead, if any.
//! Returns same value as computed by compute_allocated_size(size).
virtual size_t allocated_size(void* ptr) const;

private:
struct ChunkHeader {
Expand Down
11 changes: 10 additions & 1 deletion src/internal_modules/roc_core/iarena.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,16 @@ class IArena {
virtual void* allocate(size_t size) = 0;

//! Deallocate previously allocated memory.
virtual void deallocate(void*) = 0;
virtual void deallocate(void* ptr) = 0;

//! Computes how many bytes will be actually allocated if allocate() is called with
//! given size. Covers all internal overhead, if any.
virtual size_t compute_allocated_size(size_t size) const = 0;

//! Returns how many bytes was allocated for given pointer returned by allocate().
//! Covers all internal overhead, if any.
//! Returns same value as computed by compute_allocated_size(size).
virtual size_t allocated_size(void* ptr) const = 0;

//! Destroy object and deallocate its memory.
template <class T> void destroy_object(T& object) {
Expand Down
7 changes: 3 additions & 4 deletions src/internal_modules/roc_core/ipool.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class IPool {
public:
virtual ~IPool();

//! Get size of object allocated by pool.
virtual size_t object_size() const = 0;
//! Get size of the allocation per object.
virtual size_t allocation_size() const = 0;

//! Reserve memory for given number of objects.
//! @returns
Expand Down Expand Up @@ -54,8 +54,7 @@ class IPool {
//! Placement new for core::IPool.
//! @note
//! nothrow forces compiler to check for NULL return value before calling ctor.
inline void* operator new(size_t size, roc::core::IPool& pool) throw() {
roc_panic_if_not(size <= pool.object_size());
inline void* operator new(size_t, roc::core::IPool& pool) throw() {
return pool.allocate();
}

Expand Down
45 changes: 45 additions & 0 deletions src/internal_modules/roc_core/limited_arena.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2023 Roc Streaming authors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#include "roc_core/limited_arena.h"

namespace roc {
namespace core {

LimitedArena::LimitedArena(IArena& arena, MemoryLimiter& memory_limiter)
: arena_(arena)
, memory_limiter_(memory_limiter) {
}

void* LimitedArena::allocate(size_t size) {
size_t allocated_size = arena_.compute_allocated_size(size);
if (memory_limiter_.acquire(allocated_size)) {
void* ptr = arena_.allocate(size);
if (!ptr)
memory_limiter_.release(allocated_size);
return ptr;
}
return NULL;
}

void LimitedArena::deallocate(void* ptr) {
size_t allocated_size = arena_.allocated_size(ptr);
arena_.deallocate(ptr);
memory_limiter_.release(allocated_size);
}

size_t LimitedArena::compute_allocated_size(size_t size) const {
return arena_.compute_allocated_size(size);
}

size_t LimitedArena::allocated_size(void* ptr) const {
return arena_.allocated_size(ptr);
}

} // namespace core
} // namespace roc
54 changes: 54 additions & 0 deletions src/internal_modules/roc_core/limited_arena.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 Roc Streaming authors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

//! @file roc_core/limited_arena.h
//! @brief Limited Arena.

#ifndef ROC_CORE_LIMITED_ARENA_H_
#define ROC_CORE_LIMITED_ARENA_H_

#include "roc_core/iarena.h"
#include "roc_core/memory_limiter.h"
#include "roc_core/noncopyable.h"

namespace roc {
namespace core {

//! Decorator around IArena to make it memory limited.
class LimitedArena : public NonCopyable<>, public IArena {
public:
//! Initialize.
LimitedArena(IArena& arena, MemoryLimiter& memory_limiter);

//! Allocate memory after checking with the memory limiter.
//! @returns
//! pointer to a maximum aligned uninitialized memory at least of @p size
//! bytes or NULL if memory can't be allocated.
virtual void* allocate(size_t size);

//! Deallocate previously allocated memory.
virtual void deallocate(void* ptr);

//! Computes how many bytes will be actually allocated if allocate() is called with
//! given size. Covers all internal overhead, if any.
virtual size_t compute_allocated_size(size_t size) const;

//! Returns how many bytes was allocated for given pointer returned by allocate().
//! Covers all internal overhead, if any.
//! Returns same value as computed by compute_allocated_size(size).
virtual size_t allocated_size(void* ptr) const;

private:
IArena& arena_;
MemoryLimiter& memory_limiter_;
};

} // namespace core
} // namespace roc

#endif // ROC_CORE_LIMITED_ARENA_H_
44 changes: 44 additions & 0 deletions src/internal_modules/roc_core/limited_pool.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Roc Streaming authors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#include "roc_core/limited_pool.h"

namespace roc {
namespace core {

LimitedPool::LimitedPool(IPool& pool, MemoryLimiter& memory_limiter)
: pool_(pool)
, memory_limiter_(memory_limiter) {
}

size_t LimitedPool::allocation_size() const {
return pool_.allocation_size();
}

ROC_ATTR_NODISCARD bool LimitedPool::reserve(size_t n_objects) {
return pool_.reserve(n_objects);
}

void* LimitedPool::allocate() {
size_t allocation_size = pool_.allocation_size();
if (memory_limiter_.acquire(allocation_size)) {
void* ptr = pool_.allocate();
if (!ptr)
memory_limiter_.release(allocation_size);
return ptr;
}
return NULL;
}

void LimitedPool::deallocate(void* memory) {
pool_.deallocate(memory);
memory_limiter_.release(pool_.allocation_size());
}

} // namespace core
} // namespace roc
53 changes: 53 additions & 0 deletions src/internal_modules/roc_core/limited_pool.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 Roc Streaming authors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

//! @file roc_core/limited_pool.h
//! @brief Limited Pool.

#ifndef ROC_CORE_LIMITED_POOL_H_
#define ROC_CORE_LIMITED_POOL_H_

#include "roc_core/ipool.h"
#include "roc_core/memory_limiter.h"
#include "roc_core/noncopyable.h"

namespace roc {
namespace core {

//! Decorator around IPool to make it memory limited.
class LimitedPool : public NonCopyable<LimitedPool>, public IPool {
public:
//! Initialize.
LimitedPool(IPool& pool, MemoryLimiter& memory_limiter);

//! Get size of the allocation per object.
size_t allocation_size() const;

//! Reserve memory for given number of objects.
//! @returns
//! false if allocation failed.
ROC_ATTR_NODISCARD bool reserve(size_t n_objects);

//! Allocate memory for an object, after checking with the memory limiter.
//! @returns
//! pointer to a maximum aligned uninitialized memory for a new object
//! or NULL if memory can't be allocated.
void* allocate();

//! Return memory to pool, then update the memory limiter.
void deallocate(void* memory);

private:
IPool& pool_;
MemoryLimiter& memory_limiter_;
};

} // namespace core
} // namespace roc

#endif // ROC_CORE_LIMITED_POOL_H_
73 changes: 73 additions & 0 deletions src/internal_modules/roc_core/memory_limiter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2023 Roc Streaming authors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#include "roc_core/memory_limiter.h"

#include "roc_core/cpu_instructions.h"
#include "roc_core/log.h"

namespace roc {
namespace core {

MemoryLimiter::MemoryLimiter(const char* name, size_t max_bytes)
: name_(name)
, max_bytes_(max_bytes)
, bytes_acquired_(0) {
}

MemoryLimiter::~MemoryLimiter() {
if (bytes_acquired_ > 0) {
roc_panic("memory limiter (%s): detected that memory has not been released: "
"acquired=%lu",
name_, (unsigned long)bytes_acquired_);
}
}

bool MemoryLimiter::acquire(size_t num_bytes) {
if (num_bytes == 0) {
roc_panic("memory limiter (%s): tried to acquire zero bytes", name_);
}
size_t current;
do {
current = bytes_acquired_;
size_t next = current + num_bytes;
if (max_bytes_ > 0 && next > max_bytes_) {
break;
}
if (bytes_acquired_.compare_exchange(current, next)) {
return true;
}
cpu_relax();
} while (true);
roc_log(LogError,
"memory limiter (%s): could not acquire bytes due to limit: requested=%lu "
"acquired=%lu limit=%lu",
name_, (unsigned long)num_bytes, (unsigned long)current,
(unsigned long)max_bytes_);
return false;
}

void MemoryLimiter::release(size_t num_bytes) {
if (num_bytes == 0) {
roc_panic("memory limiter (%s): tried to release zero bytes", name_);
}
size_t next = bytes_acquired_ -= num_bytes;
size_t prev = next + num_bytes;
if (next > prev) {
roc_panic("memory limiter (%s): tried to release too many bytes: requested=%lu, "
"acquired=%lu",
name_, (unsigned long)num_bytes, (unsigned long)prev);
}
}

size_t MemoryLimiter::num_acquired() {
return bytes_acquired_;
}

} // namespace core
} // namespace roc
Loading

0 comments on commit 68a5da3

Please sign in to comment.