From 524acb17a191b404c988efd42b2eb010876076d2 Mon Sep 17 00:00:00 2001 From: Kostas Kyrimis <kostaskyrim@gmail.com> Date: Mon, 7 Jun 2021 15:45:05 +0300 Subject: [PATCH] Add global version allocators for C in query modules (#162) --- include/mg_procedure.h | 133 ++++++++---------- src/query/procedure/mg_procedure_impl.cpp | 37 +++-- src/query/procedure/module.cpp | 3 + src/query/procedure/module.hpp | 4 + tests/e2e/memory/CMakeLists.txt | 9 ++ .../e2e/memory/memory_limit_global_alloc.cpp | 26 ++++ .../memory/memory_limit_global_alloc_proc.cpp | 31 ++++ tests/e2e/memory/procedures/CMakeLists.txt | 5 + .../memory/procedures/global_memory_limit.c | 36 +++++ .../procedures/global_memory_limit_proc.c | 63 +++++++++ tests/e2e/memory/workloads.yaml | 10 ++ tests/e2e/runner.py | 4 + 12 files changed, 279 insertions(+), 82 deletions(-) create mode 100644 tests/e2e/memory/memory_limit_global_alloc.cpp create mode 100644 tests/e2e/memory/memory_limit_global_alloc_proc.cpp create mode 100644 tests/e2e/memory/procedures/CMakeLists.txt create mode 100644 tests/e2e/memory/procedures/global_memory_limit.c create mode 100644 tests/e2e/memory/procedures/global_memory_limit_proc.c diff --git a/include/mg_procedure.h b/include/mg_procedure.h index 2a2567aba..3b0168bcb 100644 --- a/include/mg_procedure.h +++ b/include/mg_procedure.h @@ -17,10 +17,11 @@ extern "C" { /// addition to efficiency, Memgraph can set the limit on allowed allocations /// thus providing some safety with regards to memory usage. The allocated /// memory is only valid during the execution of mgp_main. You must not allocate -/// global resources with these functions. None of the functions are +/// global resources with these functions and none of the functions are /// thread-safe, because we provide a single thread of execution when invoking a -/// custom procedure. This allows Memgraph to be more efficient as stated -/// before. +/// custom procedure. For allocating global resources, you can use the _global +/// variations of the aforementioned allocators. This allows Memgraph to be +/// more efficient as explained before. ///@{ /// Provides memory managament access and state. @@ -39,8 +40,7 @@ void *mgp_alloc(struct mgp_memory *memory, size_t size_in_bytes); /// `alignment` must be a power of 2 value. /// The returned pointer must be freed with mgp_free. /// NULL is returned if unable to serve the requested allocation. -void *mgp_aligned_alloc(struct mgp_memory *memory, size_t size_in_bytes, - size_t alignment); +void *mgp_aligned_alloc(struct mgp_memory *memory, size_t size_in_bytes, size_t alignment); /// Deallocate an allocation from mgp_alloc or mgp_aligned_alloc. /// Unlike free, this function is not thread-safe. @@ -48,6 +48,26 @@ void *mgp_aligned_alloc(struct mgp_memory *memory, size_t size_in_bytes, /// The behavior is undefined if `ptr` is not a value returned from a prior /// mgp_alloc or mgp_aligned_alloc call with the corresponding `memory`. void mgp_free(struct mgp_memory *memory, void *ptr); + +/// Allocate a global block of memory with given size in bytes. +/// This function can be used to allocate global memory that persists +/// beyond a single invocation of mgp_main. +/// The returned pointer must be freed with mgp_global_free. +/// NULL is returned if unable to serve the requested allocation. +void *mgp_global_alloc(size_t size_in_bytes); + +/// Allocate an aligned global block of memory with given size in bytes. +/// This function can be used to allocate global memory that persists +/// beyond a single invocation of mgp_main. +/// The returned pointer must be freed with mgp_global_free. +/// NULL is returned if unable to serve the requested allocation. +void *mgp_global_aligned_alloc(size_t size_in_bytes, size_t alignment); + +/// Deallocate an allocation from mgp_global_alloc or mgp_global_aligned_alloc. +/// If `ptr` is NULL, this function does nothing. +/// The behavior is undefined if `ptr` is not a value returned from a prior +/// mgp_global_alloc() or mgp_global_aligned_alloc(). +void mgp_global_free(void *p); ///@} /// @name Operations on mgp_value @@ -119,8 +139,7 @@ struct mgp_value *mgp_value_make_double(double val, struct mgp_memory *memory); /// Construct a character string value from a NULL terminated string. /// You need to free the instance through mgp_value_destroy. /// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_value_make_string(const char *val, - struct mgp_memory *memory); +struct mgp_value *mgp_value_make_string(const char *val, struct mgp_memory *memory); /// Create a mgp_value storing a mgp_list. /// You need to free the instance through mgp_value_destroy. The ownership of @@ -238,8 +257,7 @@ const struct mgp_path *mgp_value_get_path(const struct mgp_value *val); /// of mgp_value, but it will not contain any elements. Therefore, /// mgp_list_size will return 0. /// NULL is returned if unable to allocate a new list. -struct mgp_list *mgp_list_make_empty(size_t capacity, - struct mgp_memory *memory); +struct mgp_list *mgp_list_make_empty(size_t capacity, struct mgp_memory *memory); /// Free the memory used by the given mgp_list and contained elements. void mgp_list_destroy(struct mgp_list *list); @@ -288,8 +306,7 @@ void mgp_map_destroy(struct mgp_map *map); /// you still need to free their memory explicitly. /// Return non-zero on success, or 0 if there's no memory to insert a new /// mapping or a previous mapping already exists. -int mgp_map_insert(struct mgp_map *map, const char *key, - const struct mgp_value *value); +int mgp_map_insert(struct mgp_map *map, const char *key, const struct mgp_value *value); /// Return the number of items stored in mgp_map. size_t mgp_map_size(const struct mgp_map *map); @@ -314,8 +331,7 @@ struct mgp_map_items_iterator; /// The returned mgp_map_items_iterator needs to be deallocated with /// mgp_map_items_iterator_destroy. /// NULL is returned if unable to allocate a new iterator. -struct mgp_map_items_iterator *mgp_map_iter_items(const struct mgp_map *map, - struct mgp_memory *memory); +struct mgp_map_items_iterator *mgp_map_iter_items(const struct mgp_map *map, struct mgp_memory *memory); /// Deallocate memory used by mgp_map_items_iterator. void mgp_map_items_iterator_destroy(struct mgp_map_items_iterator *it); @@ -328,27 +344,23 @@ void mgp_map_items_iterator_destroy(struct mgp_map_items_iterator *it); /// as the value before, and use them after invoking /// mgp_map_items_iterator_next. /// NULL is returned if the end of the iteration has been reached. -const struct mgp_map_item *mgp_map_items_iterator_get( - const struct mgp_map_items_iterator *it); +const struct mgp_map_item *mgp_map_items_iterator_get(const struct mgp_map_items_iterator *it); /// Advance the iterator to the next item stored in map and return it. /// The previous pointer obtained through mgp_map_items_iterator_get will /// be invalidated, but the pointers to key and value will remain valid. /// NULL is returned if the end of the iteration has been reached. -const struct mgp_map_item *mgp_map_items_iterator_next( - struct mgp_map_items_iterator *it); +const struct mgp_map_item *mgp_map_items_iterator_next(struct mgp_map_items_iterator *it); /// Create a path with the copy of the given starting vertex. /// You need to free the created instance with mgp_path_destroy. /// NULL is returned if unable to allocate a path. -struct mgp_path *mgp_path_make_with_start(const struct mgp_vertex *vertex, - struct mgp_memory *memory); +struct mgp_path *mgp_path_make_with_start(const struct mgp_vertex *vertex, struct mgp_memory *memory); /// Copy a mgp_path. /// Returned pointer must be freed with mgp_path_destroy. /// NULL is returned if unable to allocate a mgp_path. -struct mgp_path *mgp_path_copy(const struct mgp_path *path, - struct mgp_memory *memory); +struct mgp_path *mgp_path_copy(const struct mgp_path *path, struct mgp_memory *memory); /// Free the memory used by the given mgp_path and contained vertices and edges. void mgp_path_destroy(struct mgp_path *path); @@ -370,14 +382,12 @@ size_t mgp_path_size(const struct mgp_path *path); /// Return the vertex from a path at given index. /// The valid index range is [0, mgp_path_size]. /// NULL is returned if index is out of range. -const struct mgp_vertex *mgp_path_vertex_at(const struct mgp_path *path, - size_t index); +const struct mgp_vertex *mgp_path_vertex_at(const struct mgp_path *path, size_t index); /// Return the edge from a path at given index. /// The valid index range is [0, mgp_path_size - 1]. /// NULL is returned if index is out of range. -const struct mgp_edge *mgp_path_edge_at(const struct mgp_path *path, - size_t index); +const struct mgp_edge *mgp_path_edge_at(const struct mgp_path *path, size_t index); /// Return non-zero if given paths are equal, otherwise 0. int mgp_path_equal(const struct mgp_path *p1, const struct mgp_path *p2); @@ -408,9 +418,7 @@ struct mgp_result_record *mgp_result_new_record(struct mgp_result *res); /// Return 0 if there's no memory to copy the mgp_value to mgp_result_record or /// if the combination of `field_name` and `val` does not satisfy the /// procedure's result signature. -int mgp_result_record_insert(struct mgp_result_record *record, - const char *field_name, - const struct mgp_value *val); +int mgp_result_record_insert(struct mgp_result_record *record, const char *field_name, const struct mgp_value *val); ///@} /// @name Graph Constructs @@ -446,15 +454,13 @@ struct mgp_property { /// When the mgp_properties_iterator_next is invoked, the previous /// mgp_property is invalidated and its value must not be used. /// NULL is returned if the end of the iteration has been reached. -const struct mgp_property *mgp_properties_iterator_get( - const struct mgp_properties_iterator *it); +const struct mgp_property *mgp_properties_iterator_get(const struct mgp_properties_iterator *it); /// Advance the iterator to the next property and return it. /// The previous mgp_property obtained through mgp_properties_iterator_get /// will be invalidated, and you must not use its value. /// NULL is returned if the end of the iteration has been reached. -const struct mgp_property *mgp_properties_iterator_next( - struct mgp_properties_iterator *it); +const struct mgp_property *mgp_properties_iterator_next(struct mgp_properties_iterator *it); /// Iterator over edges of a vertex. struct mgp_edges_iterator; @@ -475,8 +481,7 @@ struct mgp_vertex_id mgp_vertex_get_id(const struct mgp_vertex *v); /// Copy a mgp_vertex. /// Returned pointer must be freed with mgp_vertex_destroy. /// NULL is returned if unable to allocate a mgp_vertex. -struct mgp_vertex *mgp_vertex_copy(const struct mgp_vertex *v, - struct mgp_memory *memory); +struct mgp_vertex *mgp_vertex_copy(const struct mgp_vertex *v, struct mgp_memory *memory); /// Free the memory used by a mgp_vertex. void mgp_vertex_destroy(struct mgp_vertex *v); @@ -495,43 +500,37 @@ struct mgp_label mgp_vertex_label_at(const struct mgp_vertex *v, size_t index); int mgp_vertex_has_label(const struct mgp_vertex *v, struct mgp_label label); /// Return non-zero if the given vertex has a label with given name. -int mgp_vertex_has_label_named(const struct mgp_vertex *v, - const char *label_name); +int mgp_vertex_has_label_named(const struct mgp_vertex *v, const char *label_name); /// Get a copy of a vertex property mapped to a given name. /// Returned value must be freed with mgp_value_destroy. /// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_vertex_get_property(const struct mgp_vertex *v, - const char *property_name, +struct mgp_value *mgp_vertex_get_property(const struct mgp_vertex *v, const char *property_name, struct mgp_memory *memory); /// Start iterating over properties stored in the given vertex. /// The returned mgp_properties_iterator needs to be deallocated with /// mgp_properties_iterator_destroy. /// NULL is returned if unable to allocate a new iterator. -struct mgp_properties_iterator *mgp_vertex_iter_properties( - const struct mgp_vertex *v, struct mgp_memory *memory); +struct mgp_properties_iterator *mgp_vertex_iter_properties(const struct mgp_vertex *v, struct mgp_memory *memory); /// Start iterating over inbound edges of the given vertex. /// The returned mgp_edges_iterator needs to be deallocated with /// mgp_edges_iterator_destroy. /// NULL is returned if unable to allocate a new iterator. -struct mgp_edges_iterator *mgp_vertex_iter_in_edges(const struct mgp_vertex *v, - struct mgp_memory *memory); +struct mgp_edges_iterator *mgp_vertex_iter_in_edges(const struct mgp_vertex *v, struct mgp_memory *memory); /// Start iterating over outbound edges of the given vertex. /// The returned mgp_edges_iterator needs to be deallocated with /// mgp_edges_iterator_destroy. /// NULL is returned if unable to allocate a new iterator. -struct mgp_edges_iterator *mgp_vertex_iter_out_edges(const struct mgp_vertex *v, - struct mgp_memory *memory); +struct mgp_edges_iterator *mgp_vertex_iter_out_edges(const struct mgp_vertex *v, struct mgp_memory *memory); /// Get the current edge pointed to by the iterator. /// When the mgp_edges_iterator_next is invoked, the previous /// mgp_edge is invalidated and its value must not be used. /// NULL is returned if the end of the iteration has been reached. -const struct mgp_edge *mgp_edges_iterator_get( - const struct mgp_edges_iterator *it); +const struct mgp_edge *mgp_edges_iterator_get(const struct mgp_edges_iterator *it); /// Advance the iterator to the next edge and return it. /// The previous mgp_edge obtained through mgp_edges_iterator_get @@ -552,8 +551,7 @@ struct mgp_edge_id mgp_edge_get_id(const struct mgp_edge *e); /// Copy a mgp_edge. /// Returned pointer must be freed with mgp_edge_destroy. /// NULL is returned if unable to allocate a mgp_edge. -struct mgp_edge *mgp_edge_copy(const struct mgp_edge *e, - struct mgp_memory *memory); +struct mgp_edge *mgp_edge_copy(const struct mgp_edge *e, struct mgp_memory *memory); /// Free the memory used by a mgp_edge. void mgp_edge_destroy(struct mgp_edge *e); @@ -573,16 +571,13 @@ const struct mgp_vertex *mgp_edge_get_to(const struct mgp_edge *e); /// Get a copy of a edge property mapped to a given name. /// Returned value must be freed with mgp_value_destroy. /// NULL is returned if unable to allocate a mgp_value. -struct mgp_value *mgp_edge_get_property(const struct mgp_edge *e, - const char *property_name, - struct mgp_memory *memory); +struct mgp_value *mgp_edge_get_property(const struct mgp_edge *e, const char *property_name, struct mgp_memory *memory); /// Start iterating over properties stored in the given edge. /// The returned mgp_properties_iterator needs to be deallocated with /// mgp_properties_iterator_destroy. /// NULL is returned if unable to allocate a new iterator. -struct mgp_properties_iterator *mgp_edge_iter_properties( - const struct mgp_edge *e, struct mgp_memory *memory); +struct mgp_properties_iterator *mgp_edge_iter_properties(const struct mgp_edge *e, struct mgp_memory *memory); /// State of the graph database. struct mgp_graph; @@ -590,8 +585,7 @@ struct mgp_graph; /// Return the vertex corresponding to given ID. /// The returned vertex must be freed using mgp_vertex_destroy. /// NULL is returned if unable to allocate the vertex or if ID is not valid. -struct mgp_vertex *mgp_graph_get_vertex_by_id(const struct mgp_graph *g, - struct mgp_vertex_id id, +struct mgp_vertex *mgp_graph_get_vertex_by_id(const struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory); /// Iterator over vertices. @@ -604,22 +598,19 @@ void mgp_vertices_iterator_destroy(struct mgp_vertices_iterator *it); /// The returned mgp_vertices_iterator needs to be deallocated with /// mgp_vertices_iterator_destroy. /// NULL is returned if unable to allocate a new iterator. -struct mgp_vertices_iterator *mgp_graph_iter_vertices( - const struct mgp_graph *g, struct mgp_memory *memory); +struct mgp_vertices_iterator *mgp_graph_iter_vertices(const struct mgp_graph *g, struct mgp_memory *memory); /// Get the current vertex pointed to by the iterator. /// When the mgp_vertices_iterator_next is invoked, the previous /// mgp_vertex is invalidated and its value must not be used. /// NULL is returned if the end of the iteration has been reached. -const struct mgp_vertex *mgp_vertices_iterator_get( - const struct mgp_vertices_iterator *it); +const struct mgp_vertex *mgp_vertices_iterator_get(const struct mgp_vertices_iterator *it); /// Advance the iterator to the next vertex and return it. /// The previous mgp_vertex obtained through mgp_vertices_iterator_get /// will be invalidated, and you must not use its value. /// NULL is returned if the end of the iteration has been reached. -const struct mgp_vertex *mgp_vertices_iterator_next( - struct mgp_vertices_iterator *it); +const struct mgp_vertex *mgp_vertices_iterator_next(struct mgp_vertices_iterator *it); ///@} /// @name Type System @@ -718,8 +709,8 @@ struct mgp_proc; /// Passed in arguments will not live longer than the callback's execution. /// Therefore, you must not store them globally or use the passed in mgp_memory /// to allocate global resources. -typedef void (*mgp_proc_cb)(const struct mgp_list *, const struct mgp_graph *, - struct mgp_result *, struct mgp_memory *); +typedef void (*mgp_proc_cb)(const struct mgp_list *, const struct mgp_graph *, struct mgp_result *, + struct mgp_memory *); /// Register a read-only procedure with a module. /// @@ -730,9 +721,7 @@ typedef void (*mgp_proc_cb)(const struct mgp_list *, const struct mgp_graph *, /// /// NULL is returned if unable to allocate memory for mgp_proc; if `name` is /// not valid or a procedure with the same name was already registered. -struct mgp_proc *mgp_module_add_read_procedure(struct mgp_module *module, - const char *name, - mgp_proc_cb cb); +struct mgp_proc *mgp_module_add_read_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb); /// Add a required argument to a procedure. /// @@ -748,8 +737,7 @@ struct mgp_proc *mgp_module_add_read_procedure(struct mgp_module *module, /// 0 is returned if unable to allocate memory for an argument; if invoking this /// function after setting an optional argument or if `name` is not valid. /// Non-zero is returned on success. -int mgp_proc_add_arg(struct mgp_proc *proc, const char *name, - const struct mgp_type *type); +int mgp_proc_add_arg(struct mgp_proc *proc, const char *name, const struct mgp_type *type); /// Add an optional argument with a default value to a procedure. /// @@ -772,8 +760,7 @@ int mgp_proc_add_arg(struct mgp_proc *proc, const char *name, /// 0 is returned if unable to allocate memory for an argument; if `name` is /// not valid or `default_value` does not satisfy `type`. Non-zero is returned /// on success. -int mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, - const struct mgp_type *type, +int mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, const struct mgp_type *type, const struct mgp_value *default_value); /// Add a result field to a procedure. @@ -787,15 +774,13 @@ int mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, /// 0 is returned if unable to allocate memory for a result field; if /// `name` is not valid or if a result field with the same name was already /// added. Non-zero is returned on success. -int mgp_proc_add_result(struct mgp_proc *proc, const char *name, - const struct mgp_type *type); +int mgp_proc_add_result(struct mgp_proc *proc, const char *name, const struct mgp_type *type); /// Add a result field to a procedure and mark it as deprecated. /// /// This is the same as mgp_proc_add_result, but the result field will be marked /// as deprecated. -int mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, - const struct mgp_type *type); +int mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, const struct mgp_type *type); ///@} /// @name Execution diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index 98e7d66aa..e65f9437a 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -6,21 +6,20 @@ #include <regex> #include <type_traits> +#include "module.hpp" #include "utils/algorithm.hpp" #include "utils/logging.hpp" #include "utils/math.hpp" +#include "utils/memory.hpp" #include "utils/string.hpp" - // This file contains implementation of top level C API functions, but this is // all actually part of query::procedure. So use that namespace for simplicity. // NOLINTNEXTLINE(google-build-using-namespace) using namespace query::procedure; -void *mgp_alloc(mgp_memory *memory, size_t size_in_bytes) { - return mgp_aligned_alloc(memory, size_in_bytes, alignof(std::max_align_t)); -} +namespace { -void *mgp_aligned_alloc(mgp_memory *memory, const size_t size_in_bytes, const size_t alignment) { +void *MgpAlignedAllocImpl(utils::MemoryResource &memory, const size_t size_in_bytes, const size_t alignment) { if (size_in_bytes == 0U || !utils::IsPow2(alignment)) return nullptr; // Simplify alignment by always using values greater or equal to max_align. const size_t alloc_align = std::max(alignment, alignof(std::max_align_t)); @@ -37,7 +36,7 @@ void *mgp_aligned_alloc(mgp_memory *memory, const size_t size_in_bytes, const si const size_t alloc_size = bytes_for_header + size_in_bytes; if (alloc_size < size_in_bytes) return nullptr; try { - void *ptr = memory->impl->Allocate(alloc_size, alloc_align); + void *ptr = memory.Allocate(alloc_size, alloc_align); char *data = reinterpret_cast<char *>(ptr) + bytes_for_header; std::memcpy(data - sizeof(size_in_bytes), &size_in_bytes, sizeof(size_in_bytes)); std::memcpy(data - sizeof(size_in_bytes) - sizeof(alloc_align), &alloc_align, sizeof(alloc_align)); @@ -47,7 +46,7 @@ void *mgp_aligned_alloc(mgp_memory *memory, const size_t size_in_bytes, const si } } -void mgp_free(mgp_memory *memory, void *const p) { +void MgpFreeImpl(utils::MemoryResource &memory, void *const p) { if (!p) return; char *const data = reinterpret_cast<char *>(p); // Read the header containing size & alignment info. @@ -63,9 +62,31 @@ void mgp_free(mgp_memory *memory, void *const p) { const size_t alloc_size = bytes_for_header + size_in_bytes; // Get the original ptr we allocated. void *const original_ptr = data - bytes_for_header; - memory->impl->Deallocate(original_ptr, alloc_size, alloc_align); + memory.Deallocate(original_ptr, alloc_size, alloc_align); } +} // namespace + +void *mgp_alloc(mgp_memory *memory, size_t size_in_bytes) { + return mgp_aligned_alloc(memory, size_in_bytes, alignof(std::max_align_t)); +} + +void *mgp_aligned_alloc(mgp_memory *memory, const size_t size_in_bytes, const size_t alignment) { + return MgpAlignedAllocImpl(*memory->impl, size_in_bytes, alignment); +} + +void mgp_free(mgp_memory *memory, void *const p) { MgpFreeImpl(*memory->impl, p); } + +void *mgp_global_alloc(size_t size_in_bytes) { + return mgp_global_aligned_alloc(size_in_bytes, alignof(std::max_align_t)); +} + +void *mgp_global_aligned_alloc(size_t size_in_bytes, size_t alignment) { + return MgpAlignedAllocImpl(gModuleRegistry.GetSharedMemoryResource(), size_in_bytes, alignment); +} + +void mgp_global_free(void *const p) { MgpFreeImpl(gModuleRegistry.GetSharedMemoryResource(), p); } + namespace { // May throw whatever the constructor of U throws. `std::bad_alloc` is handled diff --git a/src/query/procedure/module.cpp b/src/query/procedure/module.cpp index 99bc54dc9..a0c9a4103 100644 --- a/src/query/procedure/module.cpp +++ b/src/query/procedure/module.cpp @@ -1,4 +1,5 @@ #include "query/procedure/module.hpp" +#include "utils/memory.hpp" extern "C" { #include <dlfcn.h> @@ -478,6 +479,8 @@ void ModuleRegistry::UnloadAllModules() { DoUnloadAllModules(); } +utils::MemoryResource &ModuleRegistry::GetSharedMemoryResource() { return *shared_; } + std::optional<std::pair<procedure::ModulePtr, const mgp_proc *>> FindProcedure( const ModuleRegistry &module_registry, const std::string_view &fully_qualified_procedure_name, utils::MemoryResource *memory) { diff --git a/src/query/procedure/module.hpp b/src/query/procedure/module.hpp index cdae588c8..0a96c7d83 100644 --- a/src/query/procedure/module.hpp +++ b/src/query/procedure/module.hpp @@ -52,6 +52,7 @@ class ModulePtr final { class ModuleRegistry final { std::map<std::string, std::unique_ptr<Module>, std::less<>> modules_; mutable utils::RWLock lock_{utils::RWLock::Priority::WRITE}; + std::unique_ptr<utils::MemoryResource> shared_{std::make_unique<utils::ResourceWithOutOfMemoryException>()}; bool RegisterModule(const std::string_view &name, std::unique_ptr<Module> module); @@ -96,6 +97,9 @@ class ModuleRegistry final { /// Takes a write lock. void UnloadAllModules(); + /// Returns the shared memory allocator used by modules + utils::MemoryResource &GetSharedMemoryResource(); + private: std::vector<std::filesystem::path> modules_dirs_; }; diff --git a/tests/e2e/memory/CMakeLists.txt b/tests/e2e/memory/CMakeLists.txt index 4e258f61a..95f3a145c 100644 --- a/tests/e2e/memory/CMakeLists.txt +++ b/tests/e2e/memory/CMakeLists.txt @@ -1,2 +1,11 @@ +add_subdirectory(procedures) + add_executable(memgraph__e2e__memory__control memory_control.cpp) target_link_libraries(memgraph__e2e__memory__control gflags mgclient mg-utils mg-io Threads::Threads) + +add_executable(memgraph__e2e__memory__limit_global_alloc memory_limit_global_alloc.cpp) +target_link_libraries(memgraph__e2e__memory__limit_global_alloc gflags mgclient mg-utils mg-io Threads::Threads) + +add_executable(memgraph__e2e__memory__limit_global_alloc_proc memory_limit_global_alloc_proc.cpp) +target_link_libraries(memgraph__e2e__memory__limit_global_alloc_proc gflags mgclient mg-utils mg-io Threads::Threads) + diff --git a/tests/e2e/memory/memory_limit_global_alloc.cpp b/tests/e2e/memory/memory_limit_global_alloc.cpp new file mode 100644 index 000000000..0d2094fea --- /dev/null +++ b/tests/e2e/memory/memory_limit_global_alloc.cpp @@ -0,0 +1,26 @@ +#include <gflags/gflags.h> +#include <mgclient.hpp> + +#include "utils/logging.hpp" +#include "utils/timer.hpp" + +DEFINE_uint64(bolt_port, 7687, "Bolt port"); +DEFINE_uint64(timeout, 120, "Timeout seconds"); + +int main(int argc, char **argv) { + google::SetUsageMessage("Memgraph E2E Memory Limit For Global Allocators"); + gflags::ParseCommandLineFlags(&argc, &argv, true); + logging::RedirectToStderr(); + + mg::Client::Init(); + + auto client = + mg::Client::Connect({.host = "127.0.0.1", .port = static_cast<uint16_t>(FLAGS_bolt_port), .use_ssl = false}); + if (!client) { + LOG_FATAL("Failed to connect!"); + } + + bool result = client->Execute("CALL libglobal_memory_limit.procedure() YIELD *"); + MG_ASSERT(result == false); + return 0; +} diff --git a/tests/e2e/memory/memory_limit_global_alloc_proc.cpp b/tests/e2e/memory/memory_limit_global_alloc_proc.cpp new file mode 100644 index 000000000..2e57da9d6 --- /dev/null +++ b/tests/e2e/memory/memory_limit_global_alloc_proc.cpp @@ -0,0 +1,31 @@ +#include <gflags/gflags.h> +#include <mgclient.hpp> +#include <algorithm> + +#include "utils/logging.hpp" +#include "utils/timer.hpp" + +DEFINE_uint64(bolt_port, 7687, "Bolt port"); +DEFINE_uint64(timeout, 120, "Timeout seconds"); + +int main(int argc, char **argv) { + google::SetUsageMessage("Memgraph E2E Memory Limit For Global Allocators"); + gflags::ParseCommandLineFlags(&argc, &argv, true); + logging::RedirectToStderr(); + + mg::Client::Init(); + + auto client = + mg::Client::Connect({.host = "127.0.0.1", .port = static_cast<uint16_t>(FLAGS_bolt_port), .use_ssl = false}); + if (!client) { + LOG_FATAL("Failed to connect!"); + } + bool result = client->Execute("CALL libglobal_memory_limit_proc.error() YIELD *"); + auto result1 = client->FetchAll(); + MG_ASSERT(result1 != std::nullopt && result1->size() == 0); + + result = client->Execute("CALL libglobal_memory_limit_proc.success() YIELD *"); + auto result2 = client->FetchAll(); + MG_ASSERT(result2 != std::nullopt && result2->size() > 0); + return 0; +} diff --git a/tests/e2e/memory/procedures/CMakeLists.txt b/tests/e2e/memory/procedures/CMakeLists.txt new file mode 100644 index 000000000..21201e59b --- /dev/null +++ b/tests/e2e/memory/procedures/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(global_memory_limit SHARED global_memory_limit.c) +target_include_directories(global_memory_limit PRIVATE ${CMAKE_SOURCE_DIR}/include) + +add_library(global_memory_limit_proc SHARED global_memory_limit_proc.c) +target_include_directories(global_memory_limit_proc PRIVATE ${CMAKE_SOURCE_DIR}/include) diff --git a/tests/e2e/memory/procedures/global_memory_limit.c b/tests/e2e/memory/procedures/global_memory_limit.c new file mode 100644 index 000000000..a2003cb59 --- /dev/null +++ b/tests/e2e/memory/procedures/global_memory_limit.c @@ -0,0 +1,36 @@ +#include "mg_procedure.h" + +int *gVal = NULL; + +void set_error(struct mgp_result *result) { mgp_result_set_error_msg(result, "Something went wrong"); } + +static void procedure(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result, + struct mgp_memory *memory) { + struct mgp_result_record *record = mgp_result_new_record(result); + if (record == NULL) return set_error(result); + + struct mgp_value *result_msg = mgp_value_make_string("mgp_init_module allocation works", memory); + if (result_msg == NULL) return set_error(result); + + int result_inserted = mgp_result_record_insert(record, "result", result_msg); + mgp_value_destroy(result_msg); + if (!result_inserted) return set_error(result); +} + +int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { + const size_t one_gb = 1 << 30; + gVal = mgp_global_alloc(one_gb); + if (!gVal) return 1; + + struct mgp_proc *proc = mgp_module_add_read_procedure(module, "procedure", procedure); + if (!proc) return 1; + + if (!mgp_proc_add_result(proc, "result", mgp_type_string())) return 1; + + return 0; +} + +int mgp_shutdown_module() { + if (gVal) mgp_global_free(gVal); + return 0; +} diff --git a/tests/e2e/memory/procedures/global_memory_limit_proc.c b/tests/e2e/memory/procedures/global_memory_limit_proc.c new file mode 100644 index 000000000..519f11f05 --- /dev/null +++ b/tests/e2e/memory/procedures/global_memory_limit_proc.c @@ -0,0 +1,63 @@ +#include "mg_procedure.h" + +int *gVal = NULL; + +void set_error(struct mgp_result *result) { mgp_result_set_error_msg(result, "Something went wrong"); } + +void set_out_of_memory_error(struct mgp_result *result) { mgp_result_set_error_msg(result, "Out of memory"); } + +static void error(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result, + struct mgp_memory *memory) { + const size_t one_gb = 1 << 30; + if (gVal) { + mgp_global_free(gVal); + gVal = NULL; + } + if (!gVal) { + gVal = mgp_global_alloc(one_gb); + if (!gVal) return set_out_of_memory_error(result); + } + struct mgp_result_record *record = mgp_result_new_record(result); + if (record == NULL) return set_error(result); + struct mgp_value *error_value = mgp_value_make_string("ERROR", memory); + if (error_value == NULL) return set_error(result); + int result_inserted = mgp_result_record_insert(record, "error_result", error_value); + mgp_value_destroy(error_value); + if (!result_inserted) return set_error(result); +} + +static void success(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result, + struct mgp_memory *memory) { + const size_t bytes = 1024; + if (!gVal) { + gVal = mgp_global_alloc(bytes); + if (!gVal) set_out_of_memory_error(result); + } + + struct mgp_result_record *record = mgp_result_new_record(result); + if (record == NULL) return set_error(result); + struct mgp_value *success_value = mgp_value_make_string("sucess", memory); + if (success_value == NULL) return set_error(result); + int result_inserted = mgp_result_record_insert(record, "success_result", success_value); + mgp_value_destroy(success_value); + if (!result_inserted) return set_error(result); +} + +int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) { + struct mgp_proc *error_proc = mgp_module_add_read_procedure(module, "error", error); + if (!error_proc) return 1; + + if (!mgp_proc_add_result(error_proc, "error_result", mgp_type_string())) return 1; + + struct mgp_proc *succ_proc = mgp_module_add_read_procedure(module, "success", success); + if (!succ_proc) return 1; + + if (!mgp_proc_add_result(succ_proc, "success_result", mgp_type_string())) return 1; + + return 0; +} + +int mgp_shutdown_module() { + if (gVal) mgp_global_free(gVal); + return 0; +} diff --git a/tests/e2e/memory/workloads.yaml b/tests/e2e/memory/workloads.yaml index adec01260..bf7ba373e 100644 --- a/tests/e2e/memory/workloads.yaml +++ b/tests/e2e/memory/workloads.yaml @@ -13,4 +13,14 @@ workloads: args: ["--bolt-port", *bolt_port, "--timeout", "180"] <<: *template_cluster + - name: "Memory limit for modules upon loading" + binary: "tests/e2e/memory/memgraph__e2e__memory__limit_global_alloc" + args: ["--bolt-port", *bolt_port, "--timeout", "180"] + proc: "tests/e2e/memory/procedures/" + <<: *template_cluster + - name: "Memory limit for modules inside a procedure" + binary: "tests/e2e/memory/memgraph__e2e__memory__limit_global_alloc_proc" + args: ["--bolt-port", *bolt_port, "--timeout", "180"] + proc: "tests/e2e/memory/procedures/" + <<: *template_cluster diff --git a/tests/e2e/runner.py b/tests/e2e/runner.py index 36a7a34de..74b69955e 100755 --- a/tests/e2e/runner.py +++ b/tests/e2e/runner.py @@ -51,6 +51,10 @@ def run(args): mg_instances[name] = mg_instance log_file_path = os.path.join(BUILD_DIR, 'logs', config['log_file']) binary_args = config['args'] + ["--log-file", log_file_path] + if 'proc' in workload: + procdir = "--query-modules-directory=" + os.path.join(BUILD_DIR, workload['proc']) + binary_args.append(procdir) + mg_instance.start(args=binary_args) for query in config['setup_queries']: mg_instance.query(query)