memory_quota_stress_test.cc
Go to the documentation of this file.
1 // Copyright 2021 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <stddef.h>
16 
17 #include <algorithm>
18 #include <atomic>
19 #include <chrono>
20 #include <initializer_list>
21 #include <memory>
22 #include <random>
23 #include <thread>
24 #include <utility>
25 #include <vector>
26 
27 #include "absl/base/thread_annotations.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/types/optional.h"
30 
33 #include <grpc/support/log.h>
34 
38 
39 namespace grpc_core {
40 
41 namespace {
42 class StressTest {
43  public:
44  // Create a stress test with some size.
45  StressTest(size_t num_quotas, size_t num_allocators) {
46  for (size_t i = 0; i < num_quotas; ++i) {
47  quotas_.emplace_back(absl::StrCat("quota[", i, "]"));
48  }
49  std::random_device g;
50  std::uniform_int_distribution<size_t> dist(0, num_quotas - 1);
51  for (size_t i = 0; i < num_allocators; ++i) {
52  allocators_.emplace_back(quotas_[dist(g)].CreateMemoryOwner(
53  absl::StrCat("allocator[", i, "]")));
54  }
55  }
56 
57  // Run the thread for some period of time.
58  void Run(int seconds) {
59  std::vector<std::thread> threads;
60 
61  // And another few threads constantly resizing quotas.
62  threads.reserve(2 + allocators_.size());
63  for (int i = 0; i < 2; i++) threads.push_back(Run(Resizer));
64 
65  // For each (allocator, pass), start a thread continuously allocating from
66  // that allocator. Whenever the first allocation is made, schedule a
67  // reclaimer for that pass.
68  for (size_t i = 0; i < allocators_.size(); i++) {
69  auto* allocator = &allocators_[i];
70  for (ReclamationPass pass :
73  threads.push_back(Run([allocator, pass](StatePtr st) mutable {
74  if (st->RememberReservation(
75  allocator->MakeReservation(st->RandomRequest()))) {
76  allocator->PostReclaimer(
77  pass, [st](absl::optional<ReclamationSweep> sweep) {
78  if (!sweep.has_value()) return;
79  st->ForgetReservations();
80  });
81  }
82  }));
83  }
84  }
85 
86  // All threads started, wait for the alloted time.
87  std::this_thread::sleep_for(std::chrono::seconds(seconds));
88 
89  // Toggle the completion bit, and then wait for the threads.
90  done_.store(true, std::memory_order_relaxed);
91  while (!threads.empty()) {
92  threads.back().join();
93  threads.pop_back();
94  }
95  }
96 
97  private:
98  // Per-thread state.
99  // Not everything is used on every thread, but it's not terrible having the
100  // extra state around and it does simplify things somewhat.
101  class State {
102  public:
103  explicit State(StressTest* test)
104  : test_(test),
107  size_distribution_(1, 4 * 1024 * 1024),
108  quota_size_distribution_(1024 * 1024, size_t(8) * 1024 * 1024 * 1024),
109  choose_variable_size_(1, 100) {}
110 
111  // Choose a random quota, and return an owned pointer to it.
112  // Not thread-safe, only callable from the owning thread.
113  MemoryQuota* RandomQuota() {
114  return &test_->quotas_[quotas_distribution_(g_)];
115  }
116 
117  // Choose a random allocator, and return a borrowed pointer to it.
118  // Not thread-safe, only callable from the owning thread.
119  MemoryOwner* RandomAllocator() {
120  return &test_->allocators_[allocators_distribution_(g_)];
121  }
122 
123  // Random memory request size - 1% of allocations are chosen to be variable
124  // sized - the rest are fixed (since variable sized create some contention
125  // problems between allocator threads of different passes on the same
126  // allocator).
127  // Not thread-safe, only callable from the owning thread.
128  MemoryRequest RandomRequest() {
129  size_t a = size_distribution_(g_);
130  if (choose_variable_size_(g_) == 1) {
131  size_t b = size_distribution_(g_);
132  return MemoryRequest(std::min(a, b), std::max(a, b));
133  }
134  return MemoryRequest(a);
135  }
136 
137  // Choose a new size for a backing quota.
138  // Not thread-safe, only callable from the owning thread.
139  size_t RandomQuotaSize() { return quota_size_distribution_(g_); }
140 
141  // Remember a reservation, return true if it's the first remembered since
142  // the last reclamation.
143  // Thread-safe.
144  bool RememberReservation(MemoryAllocator::Reservation reservation)
146  MutexLock lock(&mu_);
147  bool was_empty = reservations_.empty();
148  reservations_.emplace_back(std::move(reservation));
149  return was_empty;
150  }
151 
152  // Return all reservations made until this moment, so that they can be
153  // dropped.
154  std::vector<MemoryAllocator::Reservation> ForgetReservations()
156  MutexLock lock(&mu_);
157  return std::move(reservations_);
158  }
159 
160  private:
161  // Owning test.
162  StressTest* const test_;
163  // Random number generator.
164  std::mt19937 g_{std::random_device()()};
165  // Distribution to choose a quota.
166  std::uniform_int_distribution<size_t> quotas_distribution_;
167  // Distribution to choose an allocator.
168  std::uniform_int_distribution<size_t> allocators_distribution_;
169  // Distribution to choose an allocation size.
170  std::uniform_int_distribution<size_t> size_distribution_;
171  // Distribution to choose a quota size.
172  std::uniform_int_distribution<size_t> quota_size_distribution_;
173  // Distribution to choose whether to make a variable-sized allocation.
174  std::uniform_int_distribution<size_t> choose_variable_size_;
175 
176  // Mutex to protect the reservation list.
178  // Reservations remembered by this thread.
179  std::vector<MemoryAllocator::Reservation> reservations_
181  };
182  // Type alias since we always pass around these shared pointers.
183  using StatePtr = std::shared_ptr<State>;
184 
185  // Choose one allocator, resize it to a randomly chosen size.
186  static void Resizer(StatePtr st) {
187  auto* quota = st->RandomQuota();
188  size_t size = st->RandomQuotaSize();
189  quota->SetSize(size);
190  }
191 
192  // Create a thread that repeatedly runs a function until the test is done.
193  // We create one instance of State that we pass as a StatePtr to said
194  // function as the current overall state for this thread.
195  // Monitors done_ to see when we should stop.
196  // Ensures there's an ExecCtx for each iteration of the loop.
197  template <typename Fn>
198  std::thread Run(Fn fn) {
199  return std::thread([this, fn]() mutable {
200  auto state = std::make_shared<State>(this);
201  while (!done_.load(std::memory_order_relaxed)) {
202  ExecCtx exec_ctx;
203  fn(state);
204  }
205  });
206  }
207 
208  // Flag for when the test is completed.
209  std::atomic<bool> done_{false};
210 
211  // Memory quotas to test against. We build this up at construction time, but
212  // then don't resize, so we can load from it continuously from all of the
213  // threads.
214  std::vector<MemoryQuota> quotas_;
215  // Memory allocators to test against. Similarly, built at construction time,
216  // and then the shape of this vector is not changed.
217  std::vector<MemoryOwner> allocators_;
218 };
219 } // namespace
220 
221 } // namespace grpc_core
222 
223 int main(int, char**) {
224  if (sizeof(void*) != 8) {
225  gpr_log(
226  GPR_ERROR,
227  "This test assumes 64-bit processors in the values it uses for sizes. "
228  "Since this test is mostly aimed at TSAN coverage, and that's mostly "
229  "platform independent, we simply skip this test in 32-bit builds.");
230  return 0;
231  }
232  grpc_core::StressTest(16, 64).Run(8);
233 }
absl::time_internal::cctz::seconds
std::chrono::duration< std::int_fast64_t > seconds
Definition: abseil-cpp/absl/time/internal/cctz/include/cctz/time_zone.h:40
log.h
MutexLock
#define MutexLock(x)
Definition: bloaty/third_party/re2/util/mutex.h:125
absl::StrCat
std::string StrCat(const AlphaNum &a, const AlphaNum &b)
Definition: abseil-cpp/absl/strings/str_cat.cc:98
mu_
Mutex mu_
Definition: memory_quota_stress_test.cc:177
grpc_core::ReclamationPass::kIdle
@ kIdle
g_
std::mt19937 g_
Definition: memory_quota_stress_test.cc:164
grpc_core
Definition: call_metric_recorder.h:31
test
Definition: spinlock_test.cc:36
ABSL_GUARDED_BY
#define ABSL_GUARDED_BY(x)
Definition: abseil-cpp/absl/base/thread_annotations.h:62
allocators_distribution_
std::uniform_int_distribution< size_t > allocators_distribution_
Definition: memory_quota_stress_test.cc:168
a
int a
Definition: abseil-cpp/absl/container/internal/hash_policy_traits_test.cc:88
main
int main(int, char **)
Definition: memory_quota_stress_test.cc:223
choose_variable_size_
std::uniform_int_distribution< size_t > choose_variable_size_
Definition: memory_quota_stress_test.cc:174
threads
static uv_thread_t * threads
Definition: threadpool.c:38
size_distribution_
std::uniform_int_distribution< size_t > size_distribution_
Definition: memory_quota_stress_test.cc:170
absl::move
constexpr absl::remove_reference_t< T > && move(T &&t) noexcept
Definition: abseil-cpp/absl/utility/utility.h:221
generate-asm-lcov.fn
fn
Definition: generate-asm-lcov.py:146
max
int max
Definition: bloaty/third_party/zlib/examples/enough.c:170
gpr_log
GPRAPI void gpr_log(const char *file, int line, gpr_log_severity severity, const char *format,...) GPR_PRINT_FORMAT_CHECK(4
done_
std::atomic< bool > done_
Definition: memory_quota_stress_test.cc:209
quotas_distribution_
std::uniform_int_distribution< size_t > quotas_distribution_
Definition: memory_quota_stress_test.cc:166
memory_request.h
GPR_ERROR
#define GPR_ERROR
Definition: include/grpc/impl/codegen/log.h:57
min
#define min(a, b)
Definition: qsort.h:83
b
uint64_t b
Definition: abseil-cpp/absl/container/internal/layout_test.cc:53
g
struct @717 g
grpc_core::ReclamationPass::kBenign
@ kBenign
ABSL_LOCKS_EXCLUDED
#define ABSL_LOCKS_EXCLUDED(...)
Definition: abseil-cpp/absl/base/thread_annotations.h:163
exec_ctx
grpc_core::ExecCtx exec_ctx
Definition: end2end_binder_transport_test.cc:75
google::protobuf.internal::Mutex
WrappedMutex Mutex
Definition: bloaty/third_party/protobuf/src/google/protobuf/stubs/mutex.h:113
state
Definition: bloaty/third_party/zlib/contrib/blast/blast.c:41
exec_ctx.h
memory_quota.h
grpc_core::ReclamationPass
ReclamationPass
Definition: memory_quota.h:62
test_
StressTest *const test_
Definition: memory_quota_stress_test.cc:162
memory_allocator.h
quotas_
std::vector< MemoryQuota > quotas_
Definition: memory_quota_stress_test.cc:214
googletest-break-on-failure-unittest.Run
def Run(command)
Definition: bloaty/third_party/googletest/googletest/test/googletest-break-on-failure-unittest.py:76
size
voidpf void uLong size
Definition: bloaty/third_party/zlib/contrib/minizip/ioapi.h:136
quota_size_distribution_
std::uniform_int_distribution< size_t > quota_size_distribution_
Definition: memory_quota_stress_test.cc:172
Fn
void Fn()
thread
static uv_thread_t thread
Definition: test-async-null-cb.c:29
sync.h
grpc_core::ReclamationPass::kDestructive
@ kDestructive
i
uint64_t i
Definition: abseil-cpp/absl/container/btree_benchmark.cc:230
allocators_
std::vector< MemoryOwner > allocators_
Definition: memory_quota_stress_test.cc:217


grpc
Author(s):
autogenerated on Fri May 16 2025 02:59:23