Skip to content

Commit 61a8a3c

Browse files
authored
Merge pull request #8 from arangodb/bugfix/iresearch-memory-management
IResearch memory management implementation
2 parents c24abc1 + 57c1670 commit 61a8a3c

File tree

4 files changed

+164
-1
lines changed

4 files changed

+164
-1
lines changed

core/resource_manager.hpp

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,17 @@
2222

2323
#pragma once
2424

25+
#include <atomic>
2526
#include <vector>
2627

2728
#include "shared.hpp"
2829
#include "utils/managed_allocator.hpp"
2930

31+
namespace arangodb::iresearch
32+
{
33+
class IResearchFeature;
34+
};
35+
3036
namespace irs {
3137

3238
struct IResourceManager {
@@ -68,6 +74,69 @@ struct ResourceManagementOptions {
6874
IResourceManager* cached_columns{&IResourceManager::kNoop};
6975
};
7076

77+
// Memory manager for IResearch.
78+
// This is a singleton class
79+
struct IResearchMemoryManager : public IResourceManager {
80+
protected:
81+
IResearchMemoryManager() = default;
82+
83+
public:
84+
virtual ~IResearchMemoryManager() = default;
85+
86+
virtual void Increase([[maybe_unused]] uint64_t value) override {
87+
88+
IRS_ASSERT(this != &kForbidden);
89+
IRS_ASSERT(value >= 0);
90+
91+
if (0 == _memoryLimit) {
92+
// since we have no limit, we can simply use fetch-add for the increment
93+
_current.fetch_add(value, std::memory_order_relaxed);
94+
} else {
95+
// we only want to perform the update if we don't exceed the limit!
96+
std::uint64_t cur = _current.load(std::memory_order_relaxed);
97+
std::uint64_t next;
98+
do {
99+
next = cur + value;
100+
if (IRS_UNLIKELY(next > _memoryLimit.load(std::memory_order_relaxed))) {
101+
throw std::bad_alloc();
102+
}
103+
} while (!_current.compare_exchange_weak(
104+
cur, next, std::memory_order_relaxed));
105+
}
106+
}
107+
108+
virtual void Decrease([[maybe_unused]] uint64_t value) noexcept override {
109+
IRS_ASSERT(this != &kForbidden);
110+
IRS_ASSERT(value >= 0);
111+
_current.fetch_sub(value, std::memory_order_relaxed);
112+
}
113+
114+
// NOTE: IResearchFeature owns and manages this memory limit.
115+
// That is why this method should only be used by IResearchFeature.
116+
virtual void SetMemoryLimit(uint64_t memoryLimit) {
117+
_memoryLimit.store(memoryLimit);
118+
}
119+
120+
private:
121+
// This limit should be set exclusively by IResearchFeature.
122+
// During IResearchFeature::validateOptions() this limit is set to a
123+
// percentage of either the total available physical memory or the value
124+
// of ARANGODB_OVERRIDE_DETECTED_TOTAL_MEMORY envvar if specified.
125+
std::atomic<std::uint64_t> _memoryLimit = { 0 };
126+
std::atomic<std::uint64_t> _current = { 0 };
127+
128+
// Singleton
129+
static inline std::shared_ptr<IResearchMemoryManager> _instance;
130+
131+
public:
132+
static std::shared_ptr<IResearchMemoryManager> GetInstance() {
133+
if (!_instance.get())
134+
_instance.reset(new IResearchMemoryManager());
135+
136+
return _instance;
137+
}
138+
};
139+
71140
template<typename T>
72141
struct ManagedTypedAllocator
73142
: ManagedAllocator<std::allocator<T>, IResourceManager> {
@@ -77,7 +146,7 @@ struct ManagedTypedAllocator
77146
#if !defined(_MSC_VER) && defined(IRESEARCH_DEBUG)
78147
IResourceManager::kForbidden
79148
#else
80-
IResourceManager::kNoop
149+
*IResearchMemoryManager::GetInstance()
81150
#endif
82151
) {
83152
}

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ set(IReSearch_tests_sources
121121
./utils/fst_utils_test.cpp
122122
./utils/fst_string_weight_test.cpp
123123
./utils/ngram_match_utils_tests.cpp
124+
./memory/IResearchMemoryManager_tests.cpp
124125
./tests_param.cpp
125126
./tests_main.cpp
126127
)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
/// DISCLAIMER
3+
///
4+
/// Copyright 2014-2025 ArangoDB GmbH, Cologne, Germany
5+
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
6+
///
7+
/// Licensed under the Apache License, Version 2.0 (the "License");
8+
/// you may not use this file except in compliance with the License.
9+
/// You may obtain a copy of the License at
10+
///
11+
/// http://www.apache.org/licenses/LICENSE-2.0
12+
///
13+
/// Unless required by applicable law or agreed to in writing, software
14+
/// distributed under the License is distributed on an "AS IS" BASIS,
15+
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
/// See the License for the specific language governing permissions and
17+
/// limitations under the License.
18+
///
19+
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
20+
///
21+
/// @author Koushal Kawade
22+
////////////////////////////////////////////////////////////////////////////////
23+
24+
#include <iostream>
25+
#include <memory>
26+
#include "tests_shared.hpp"
27+
#include "utils/managed_allocator.hpp"
28+
#include "resource_manager.hpp"
29+
30+
using namespace irs;
31+
32+
struct LargeData {
33+
char data[1024];
34+
};
35+
36+
TEST(IResearchMemoryLimitTest, no_limit_set_allows_infinite_allocations) {
37+
38+
// The expectation is to allow potentially infinite allocations
39+
// even if we're testing with just 1MB.
40+
41+
// allocate vector
42+
ManagedVector<LargeData> vec;
43+
44+
for (size_t i = 0; i < 1024; i++) {
45+
ASSERT_NO_THROW(vec.push_back(LargeData()));
46+
}
47+
}
48+
49+
TEST(IResearchMemoryLimitTest, zero_limit_allow_infinite_allocations) {
50+
51+
auto memoryMgr = IResearchMemoryManager::GetInstance();
52+
memoryMgr->SetMemoryLimit(0);
53+
54+
// allocate vector
55+
ManagedVector<LargeData> vec;
56+
57+
for (size_t i = 0; i < 1024; i++) {
58+
ASSERT_NO_THROW(vec.push_back(LargeData()));
59+
}
60+
}
61+
62+
TEST(IResearchMemoryLimitTest, managed_allocator_smoke_test) {
63+
64+
auto memoryMgr = IResearchMemoryManager::GetInstance();
65+
memoryMgr->SetMemoryLimit(3);
66+
67+
using type = char;
68+
std::vector<type, ManagedTypedAllocator<type>> arr(*memoryMgr);
69+
70+
arr.push_back('a');
71+
arr.push_back('b');
72+
ASSERT_THROW(arr.push_back('c'), std::bad_alloc);
73+
}
74+
75+
TEST(IResearchMemoryLimitTest, memory_manager_smoke_test) {
76+
77+
// set limit
78+
auto memoryMgr = IResearchMemoryManager::GetInstance();
79+
memoryMgr->SetMemoryLimit(47);
80+
81+
// allocate vector
82+
ManagedVector<uint64_t> vec;
83+
vec.push_back(10); // Allocate 8 bytes
84+
vec.push_back(11); // Allocate 16, Free previous 8, Copy both elements in the new array.
85+
86+
ASSERT_THROW(vec.push_back(12), std::bad_alloc); // Allocate 32 while holding previous 16, total 48 (bad_alloc)
87+
88+
// Increase memory limit to accommodate the 3rd element.
89+
memoryMgr->SetMemoryLimit(48);
90+
91+
ASSERT_NO_THROW(vec.push_back(12));
92+
}

tests/utils/utf8_path_tests.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <filesystem>
2727
#include <fstream>
2828
#include <thread>
29+
#include <algorithm>
2930

3031
#include "tests_shared.hpp"
3132
#include "utils/file_utils.hpp"

0 commit comments

Comments
 (0)