Skip to content

Commit

Permalink
src: port memory-tracking allocator from QUIC repo
Browse files Browse the repository at this point in the history
This implements a memory-tracking allocator that can be used to
provide memory allocation facilities to several thread-safe C
libraries, including nghttp2, nghttp3, ngtcp3 and uvwasi.

Refs: https://github.com/nodejs/quic/blob/34ee0bc96f804c73cb22b2945a1a78f780938492/src/node_mem.h
Co-authored-by: James M Snell <jasnell@gmail.com>

PR-URL: #30745
Refs: nodejs/quic#126
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
addaleax committed Dec 7, 2019
1 parent 4e11920 commit 1e5911d
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 0 deletions.
2 changes: 2 additions & 0 deletions node.gyp
Expand Up @@ -627,6 +627,8 @@
'src/node_i18n.h',
'src/node_internals.h',
'src/node_main_instance.h',
'src/node_mem.h',
'src/node_mem-inl.h',
'src/node_messaging.h',
'src/node_metadata.h',
'src/node_mutex.h',
Expand Down
112 changes: 112 additions & 0 deletions src/node_mem-inl.h
@@ -0,0 +1,112 @@
#ifndef SRC_NODE_MEM_INL_H_
#define SRC_NODE_MEM_INL_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "node_mem.h"
#include "node_internals.h"

namespace node {
namespace mem {

template <typename Class, typename AllocatorStruct>
AllocatorStruct NgLibMemoryManager<Class, AllocatorStruct>::MakeAllocator() {
return AllocatorStruct {
static_cast<void*>(static_cast<Class*>(this)),
MallocImpl,
FreeImpl,
CallocImpl,
ReallocImpl
};
}

template <typename Class, typename T>
void* NgLibMemoryManager<Class, T>::ReallocImpl(void* ptr,
size_t size,
void* user_data) {
Class* manager = static_cast<Class*>(user_data);

size_t previous_size = 0;
char* original_ptr = nullptr;

// We prepend each allocated buffer with a size_t containing the full
// size of the allocation.
if (size > 0) size += sizeof(size_t);

if (ptr != nullptr) {
// We are free()ing or re-allocating.
original_ptr = static_cast<char*>(ptr) - sizeof(size_t);
previous_size = *reinterpret_cast<size_t*>(original_ptr);
// This means we called StopTracking() on this pointer before.
if (previous_size == 0) {
// Fall back to the standard Realloc() function.
char* ret = UncheckedRealloc(original_ptr, size);
if (ret != nullptr)
ret += sizeof(size_t);
return ret;
}
}

manager->CheckAllocatedSize(previous_size);

char* mem = UncheckedRealloc(original_ptr, size);

if (mem != nullptr) {
// Adjust the memory info counter.
// TODO(addaleax): Avoid the double bookkeeping we do with
// current_nghttp2_memory_ + AdjustAmountOfExternalAllocatedMemory
// and provide versions of our memory allocation utilities that take an
// Environment*/Isolate* parameter and call the V8 method transparently.
const int64_t new_size = size - previous_size;
manager->IncreaseAllocatedSize(new_size);
manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory(
new_size);
*reinterpret_cast<size_t*>(mem) = size;
mem += sizeof(size_t);
} else if (size == 0) {
manager->DecreaseAllocatedSize(previous_size);
manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory(
-static_cast<int64_t>(previous_size));
}
return mem;
}

template <typename Class, typename T>
void* NgLibMemoryManager<Class, T>::MallocImpl(size_t size, void* user_data) {
return ReallocImpl(nullptr, size, user_data);
}

template <typename Class, typename T>
void NgLibMemoryManager<Class, T>::FreeImpl(void* ptr, void* user_data) {
if (ptr == nullptr) return;
CHECK_NULL(ReallocImpl(ptr, 0, user_data));
}

template <typename Class, typename T>
void* NgLibMemoryManager<Class, T>::CallocImpl(size_t nmemb,
size_t size,
void* user_data) {
size_t real_size = MultiplyWithOverflowCheck(nmemb, size);
void* mem = MallocImpl(real_size, user_data);
if (mem != nullptr)
memset(mem, 0, real_size);
return mem;
}

template <typename Class, typename T>
void NgLibMemoryManager<Class, T>::StopTrackingMemory(void* ptr) {
size_t* original_ptr = reinterpret_cast<size_t*>(
static_cast<char*>(ptr) - sizeof(size_t));
Class* manager = static_cast<Class*>(this);
manager->DecreaseAllocatedSize(*original_ptr);
manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory(
-static_cast<int64_t>(*original_ptr));
*original_ptr = 0;
}

} // namespace mem
} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_MEM_INL_H_
41 changes: 41 additions & 0 deletions src/node_mem.h
@@ -0,0 +1,41 @@
#ifndef SRC_NODE_MEM_H_
#define SRC_NODE_MEM_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <cstddef>

namespace node {
namespace mem {

// Both ngtcp2 and nghttp2 allow custom allocators that
// follow exactly the same structure and behavior, but
// use different struct names. To allow for code re-use,
// the NgLibMemoryManager template class can be used for both.

template <typename Class, typename AllocatorStructName>
class NgLibMemoryManager {
public:
// Class needs to provide these methods:
// void CheckAllocatedSize(size_t previous_size) const;
// void IncreaseAllocatedSize(size_t size);
// void DecreaseAllocatedSize(size_t size);
// Environment* env() const;

AllocatorStructName MakeAllocator();

void StopTrackingMemory(void* ptr);

private:
static void* ReallocImpl(void* ptr, size_t size, void* user_data);
static void* MallocImpl(size_t size, void* user_data);
static void FreeImpl(void* ptr, void* user_data);
static void* CallocImpl(size_t nmemb, size_t size, void* user_data);
};

} // namespace mem
} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_MEM_H_

0 comments on commit 1e5911d

Please sign in to comment.