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 #ifndef GRPC_SRC_CORE_LIB_RESOURCE_QUOTA_MEMORY_QUOTA_H
16 #define GRPC_SRC_CORE_LIB_RESOURCE_QUOTA_MEMORY_QUOTA_H
17
18 #include <grpc/event_engine/memory_allocator.h>
19 #include <grpc/event_engine/memory_request.h>
20 #include <grpc/support/port_platform.h>
21 #include <stdint.h>
22
23 #include <array>
24 #include <atomic>
25 #include <cstddef>
26 #include <limits>
27 #include <memory>
28 #include <string>
29 #include <utility>
30 #include <vector>
31
32 #include "absl/base/thread_annotations.h"
33 #include "absl/container/flat_hash_set.h"
34 #include "absl/log/check.h"
35 #include "absl/log/log.h"
36 #include "absl/strings/string_view.h"
37 #include "absl/types/optional.h"
38 #include "src/core/lib/debug/trace.h"
39 #include "src/core/lib/experiments/experiments.h"
40 #include "src/core/lib/promise/activity.h"
41 #include "src/core/lib/promise/poll.h"
42 #include "src/core/lib/resource_quota/periodic_update.h"
43 #include "src/core/util/orphanable.h"
44 #include "src/core/util/ref_counted_ptr.h"
45 #include "src/core/util/sync.h"
46 #include "src/core/util/time.h"
47 #include "src/core/util/useful.h"
48
49 namespace grpc_core {
50
51 class BasicMemoryQuota;
52 class MemoryQuota;
53 class GrpcMemoryAllocatorImpl;
54
55 using grpc_event_engine::experimental::MemoryRequest;
56
57 // Pull in impl under a different name to keep the gRPC/EventEngine separation
58 // clear.
59 using EventEngineMemoryAllocatorImpl =
60 grpc_event_engine::experimental::internal::MemoryAllocatorImpl;
61 using grpc_event_engine::experimental::MemoryAllocator;
62 template <typename T>
63 using Vector = grpc_event_engine::experimental::Vector<T>;
64
65 // Reclamation passes.
66 // When memory is tight, we start trying to claim some back from memory
67 // reclaimers. We do this in multiple passes: if there is a less destructive
68 // operation available, we do that, otherwise we do something more destructive.
69 enum class ReclamationPass {
70 // Benign reclamation is intended for reclamation steps that are not
71 // observable outside of gRPC (besides maybe causing an increase in CPU
72 // usage).
73 // Examples of such reclamation would be resizing buffers to fit the current
74 // load needs, rather than whatever was the peak usage requirement.
75 kBenign = 0,
76 // Idle reclamation is intended for reclamation steps that are observable
77 // outside of gRPC, but do not cause application work to be lost.
78 // Examples of such reclamation would be dropping channels that are not being
79 // used.
80 kIdle = 1,
81 // Destructive reclamation is our last resort, and is these reclamations are
82 // allowed to drop work - such as cancelling in flight requests.
83 kDestructive = 2,
84 };
85 static constexpr size_t kNumReclamationPasses = 3;
86
87 // For each reclamation function run we construct a ReclamationSweep.
88 // When this object is finally destroyed (it may be moved several times first),
89 // then that reclamation is complete and we may continue the reclamation loop.
90 class ReclamationSweep {
91 public:
92 ReclamationSweep() = default;
ReclamationSweep(std::shared_ptr<BasicMemoryQuota> memory_quota,uint64_t sweep_token,Waker waker)93 ReclamationSweep(std::shared_ptr<BasicMemoryQuota> memory_quota,
94 uint64_t sweep_token, Waker waker)
95 : memory_quota_(std::move(memory_quota)),
96 sweep_token_(sweep_token),
97 waker_(std::move(waker)) {}
98 ~ReclamationSweep();
99
100 ReclamationSweep(const ReclamationSweep&) = delete;
101 ReclamationSweep& operator=(const ReclamationSweep&) = delete;
102 ReclamationSweep(ReclamationSweep&&) = default;
103 ReclamationSweep& operator=(ReclamationSweep&&) = default;
104
105 // Has enough work been done that we would not be called upon again
106 // immediately to do reclamation work if we stopped and requeued. Reclaimers
107 // with a variable amount of work to do can use this to ascertain when they
108 // can stop more efficiently than going through the reclaimer queue once per
109 // work item.
110 bool IsSufficient() const;
111
112 // Explicit finish for users that wish to write it.
113 // Just destroying the object is enough, but sometimes the additional
114 // explicitness is warranted.
Finish()115 void Finish() {
116 [](ReclamationSweep) {}(std::move(*this));
117 }
118
119 private:
120 std::shared_ptr<BasicMemoryQuota> memory_quota_;
121 uint64_t sweep_token_;
122 Waker waker_;
123 };
124
125 class ReclaimerQueue {
126 private:
127 struct QueuedNode;
128 struct State;
129
130 public:
131 class Handle : public InternallyRefCounted<Handle> {
132 public:
133 Handle() = default;
134 template <typename F>
Handle(F reclaimer,std::shared_ptr<State> state)135 explicit Handle(F reclaimer, std::shared_ptr<State> state)
136 : sweep_(new SweepFn<F>(std::move(reclaimer), std::move(state))) {}
~Handle()137 ~Handle() override {
138 DCHECK_EQ(sweep_.load(std::memory_order_relaxed), nullptr);
139 }
140
141 Handle(const Handle&) = delete;
142 Handle& operator=(const Handle&) = delete;
143
144 void Orphan() final;
145 void Run(ReclamationSweep reclamation_sweep);
146 bool Requeue(ReclaimerQueue* new_queue);
147
148 private:
149 friend class ReclaimerQueue;
150 using InternallyRefCounted<Handle>::Ref;
151
152 class Sweep {
153 public:
154 virtual void RunAndDelete(absl::optional<ReclamationSweep> sweep) = 0;
155
156 protected:
Sweep(std::shared_ptr<State> state)157 explicit Sweep(std::shared_ptr<State> state) : state_(std::move(state)) {}
158 ~Sweep() = default;
159 void MarkCancelled();
160
161 private:
162 std::shared_ptr<State> state_;
163 };
164
165 template <typename F>
166 class SweepFn final : public Sweep {
167 public:
SweepFn(F && f,std::shared_ptr<State> state)168 explicit SweepFn(F&& f, std::shared_ptr<State> state)
169 : Sweep(std::move(state)), f_(std::move(f)) {}
RunAndDelete(absl::optional<ReclamationSweep> sweep)170 void RunAndDelete(absl::optional<ReclamationSweep> sweep) override {
171 if (!sweep.has_value()) MarkCancelled();
172 f_(std::move(sweep));
173 delete this;
174 }
175
176 private:
177 F f_;
178 };
179
180 std::atomic<Sweep*> sweep_{nullptr};
181 };
182
183 ReclaimerQueue();
184 ~ReclaimerQueue();
185
186 ReclaimerQueue(const ReclaimerQueue&) = delete;
187 ReclaimerQueue& operator=(const ReclaimerQueue&) = delete;
188
189 // Insert a new element at the back of the queue.
190 // If there is already an element from allocator at *index, then it is
191 // replaced with the new reclaimer and *index is unchanged. If there is not,
192 // then *index is set to the index of the newly queued entry.
193 // Associates the reclamation function with an allocator, and keeps that
194 // allocator alive, so that we can use the pointer as an ABA guard.
195 template <typename F>
Insert(F reclaimer)196 GRPC_MUST_USE_RESULT OrphanablePtr<Handle> Insert(F reclaimer) {
197 auto p = MakeOrphanable<Handle>(std::move(reclaimer), state_);
198 Enqueue(p->Ref());
199 return p;
200 }
201
202 // Poll to see if an entry is available: returns Pending if not, or the
203 // removed reclamation function if so.
204 Poll<RefCountedPtr<Handle>> PollNext();
205
206 // This callable is the promise backing Next - it resolves when there is an
207 // entry available. This really just redirects to calling PollNext().
208 class NextPromise {
209 public:
NextPromise(ReclaimerQueue * queue)210 explicit NextPromise(ReclaimerQueue* queue) : queue_(queue) {}
operator()211 Poll<RefCountedPtr<Handle>> operator()() { return queue_->PollNext(); }
212
213 private:
214 // Borrowed ReclaimerQueue backing this promise.
215 ReclaimerQueue* queue_;
216 };
Next()217 GRPC_MUST_USE_RESULT NextPromise Next() { return NextPromise(this); }
218
219 private:
220 void Enqueue(RefCountedPtr<Handle> handle);
221
222 std::shared_ptr<State> state_;
223 };
224
225 namespace memory_quota_detail {
226 // Controller: tries to adjust a control variable up or down to get memory
227 // pressure to some target. We use the control variable to size buffers
228 // throughout the stack.
229 class PressureController {
230 public:
PressureController(uint8_t max_ticks_same,uint8_t max_reduction_per_tick)231 PressureController(uint8_t max_ticks_same, uint8_t max_reduction_per_tick)
232 : max_ticks_same_(max_ticks_same),
233 max_reduction_per_tick_(max_reduction_per_tick) {}
234 // Update the controller, returns the new control value.
235 double Update(double error);
236 // Textual representation of the controller.
237 std::string DebugString() const;
238
239 private:
240 // How many update periods have we reached the same decision in a row?
241 // Too many and we should start expanding the search space since we're not
242 // being aggressive enough.
243 uint8_t ticks_same_ = 0;
244 // Maximum number of ticks with the same value until we start expanding the
245 // control space.
246 const uint8_t max_ticks_same_;
247 // Maximum amount to reduce the reporting value per iteration (in tenths of a
248 // percentile).
249 const uint8_t max_reduction_per_tick_;
250 // Was the last error indicating a too low pressure (or if false,
251 // a too high pressure).
252 bool last_was_low_ = true;
253 // Current minimum value to report.
254 double min_ = 0.0;
255 // Current maximum value to report.
256 // Set so that the first change over will choose 1.0 for max.
257 double max_ = 2.0;
258 // Last control value reported.
259 double last_control_ = 0.0;
260 };
261
262 // Utility to track memory pressure.
263 // Tries to be conservative (returns a higher pressure than there may actually
264 // be) but to be eventually accurate.
265 class PressureTracker {
266 public:
267 double AddSampleAndGetControlValue(double sample);
268
269 private:
270 std::atomic<double> max_this_round_{0.0};
271 std::atomic<double> report_{0.0};
272 PeriodicUpdate update_{Duration::Seconds(1)};
273 PressureController controller_{100, 3};
274 };
275 } // namespace memory_quota_detail
276
277 // Minimum number of free bytes in order for allocator to move to big bucket.
278 static constexpr size_t kBigAllocatorThreshold = 0.5 * 1024 * 1024;
279 // Maximum number of free bytes in order for allocator to move to small
280 // bucket.
281 static constexpr size_t kSmallAllocatorThreshold = 0.1 * 1024 * 1024;
282
283 class BasicMemoryQuota final
284 : public std::enable_shared_from_this<BasicMemoryQuota> {
285 public:
286 // Data about current memory pressure.
287 struct PressureInfo {
288 // The current instantaneously measured memory pressure.
289 double instantaneous_pressure = 0.0;
290 // A control value that can be used to scale buffer sizes up or down to
291 // adjust memory pressure to our target set point.
292 double pressure_control_value = 0.0;
293 // Maximum recommended individual allocation size.
294 size_t max_recommended_allocation_size = 0;
295 };
296
297 explicit BasicMemoryQuota(std::string name);
298
299 // Start the reclamation activity.
300 void Start();
301 // Stop the reclamation activity.
302 // Until reclamation is stopped, it's possible that circular references to the
303 // BasicMemoryQuota remain. i.e. to guarantee deletion, a singular owning
304 // object should call BasicMemoryQuota::Stop().
305 void Stop();
306
307 // Resize the quota to new_size.
308 void SetSize(size_t new_size);
309 // Forcefully take some memory from the quota, potentially entering
310 // overcommit.
311 void Take(GrpcMemoryAllocatorImpl* allocator, size_t amount);
312 // Finish reclamation pass.
313 void FinishReclamation(uint64_t token, Waker waker);
314 // Return some memory to the quota.
315 void Return(size_t amount);
316 // Add allocator to list of allocators in small bucket. Returns allocator id.
317 void AddNewAllocator(GrpcMemoryAllocatorImpl* allocator);
318 // Remove allocator from list of allocators.
319 void RemoveAllocator(GrpcMemoryAllocatorImpl* allocator);
320 // Determine whether to move allocator to different bucket and if so, move.
321 void MaybeMoveAllocator(GrpcMemoryAllocatorImpl* allocator,
322 size_t old_free_bytes, size_t new_free_bytes);
323 // Instantaneous memory pressure approximation.
324 PressureInfo GetPressureInfo();
325 // Get a reclamation queue
reclaimer_queue(size_t i)326 ReclaimerQueue* reclaimer_queue(size_t i) { return &reclaimers_[i]; }
327
328 // The name of this quota
name()329 absl::string_view name() const { return name_; }
330
331 private:
332 friend class ReclamationSweep;
333 class WaitForSweepPromise;
334
335 class AllocatorBucket {
336 public:
337 struct Shard {
338 absl::flat_hash_set<GrpcMemoryAllocatorImpl*> allocators
339 ABSL_GUARDED_BY(shard_mu);
340 Mutex shard_mu;
341 };
342
SelectShard(void * key)343 Shard& SelectShard(void* key) {
344 const size_t hash = HashPointer(key, shards.size());
345 return shards[hash % shards.size()];
346 }
347
348 std::array<Shard, 16> shards;
349 };
350
351 static constexpr intptr_t kInitialSize = std::numeric_limits<intptr_t>::max();
352
353 // Move allocator from big bucket to small bucket.
354 void MaybeMoveAllocatorBigToSmall(GrpcMemoryAllocatorImpl* allocator);
355 // Move allocator from small bucket to big bucket.
356 void MaybeMoveAllocatorSmallToBig(GrpcMemoryAllocatorImpl* allocator);
357
358 // The amount of memory that's free in this quota.
359 // We use intptr_t as a reasonable proxy for ssize_t that's portable.
360 // We allow arbitrary overcommit and so this must allow negative values.
361 std::atomic<intptr_t> free_bytes_{kInitialSize};
362 // The total number of bytes in this quota.
363 std::atomic<size_t> quota_size_{kInitialSize};
364
365 // Reclaimer queues.
366 ReclaimerQueue reclaimers_[kNumReclamationPasses];
367 // List of all allocators sorted into 2 buckets, small (<100 KB free bytes)
368 // and large (>500 KB free bytes).
369 AllocatorBucket small_allocators_;
370 AllocatorBucket big_allocators_;
371 // The reclaimer activity consumes reclaimers whenever we are in overcommit to
372 // try and get back under memory limits.
373 ActivityPtr reclaimer_activity_;
374 // Each time we do a reclamation sweep, we increment this counter and give it
375 // to the sweep in question. In this way, should we choose to cancel a sweep
376 // we can do so and not get confused when the sweep reports back that it's
377 // completed.
378 // We also increment this counter on completion of a sweep, as an indicator
379 // that the wait has ended.
380 std::atomic<uint64_t> reclamation_counter_{0};
381 // Memory pressure smoothing
382 memory_quota_detail::PressureTracker pressure_tracker_;
383 // The name of this quota - used for debugging/tracing/etc..
384 std::string name_;
385 };
386
387 // MemoryAllocatorImpl grants the owner the ability to allocate memory from an
388 // underlying resource quota.
389 class GrpcMemoryAllocatorImpl final : public EventEngineMemoryAllocatorImpl {
390 public:
391 explicit GrpcMemoryAllocatorImpl(
392 std::shared_ptr<BasicMemoryQuota> memory_quota);
393 ~GrpcMemoryAllocatorImpl() override;
394
395 // Reserve bytes from the quota.
396 // If we enter overcommit, reclamation will begin concurrently.
397 // Returns the number of bytes reserved.
398 size_t Reserve(MemoryRequest request) override;
399
400 /// Allocate a slice, using MemoryRequest to size the number of returned
401 /// bytes. For a variable length request, check the returned slice length to
402 /// verify how much memory was allocated. Takes care of reserving memory for
403 /// any relevant control structures also.
404 grpc_slice MakeSlice(MemoryRequest request) override;
405
406 // Release some bytes that were previously reserved.
Release(size_t n)407 void Release(size_t n) override {
408 // Add the released memory to our free bytes counter... if this increases
409 // from 0 to non-zero, then we have more to do, otherwise, we're actually
410 // done.
411 size_t prev_free = free_bytes_.fetch_add(n, std::memory_order_release);
412 if ((!IsUnconstrainedMaxQuotaBufferSizeEnabled() &&
413 prev_free + n > kMaxQuotaBufferSize) ||
414 donate_back_.Tick([](Duration) {})) {
415 // Try to immediately return some free'ed memory back to the total quota.
416 MaybeDonateBack();
417 }
418 size_t new_free = free_bytes_.load(std::memory_order_relaxed);
419 memory_quota_->MaybeMoveAllocator(this, prev_free, new_free);
420 }
421
422 // Return all free bytes to quota.
ReturnFree()423 void ReturnFree() {
424 size_t ret = free_bytes_.exchange(0, std::memory_order_acq_rel);
425 if (ret == 0) return;
426 GRPC_TRACE_LOG(resource_quota, INFO)
427 << "Allocator " << this << " returning " << ret << " bytes to quota";
428 taken_bytes_.fetch_sub(ret, std::memory_order_relaxed);
429 memory_quota_->Return(ret);
430 memory_quota_->MaybeMoveAllocator(this, /*old_free_bytes=*/ret,
431 /*new_free_bytes=*/0);
432 }
433
434 // Post a reclamation function.
435 template <typename F>
PostReclaimer(ReclamationPass pass,F fn)436 void PostReclaimer(ReclamationPass pass, F fn) {
437 MutexLock lock(&reclaimer_mu_);
438 CHECK(!shutdown_);
439 InsertReclaimer(static_cast<size_t>(pass), std::move(fn));
440 }
441
442 // Shutdown the allocator.
443 void Shutdown() override;
444
445 // Read the instantaneous memory pressure
GetPressureInfo()446 BasicMemoryQuota::PressureInfo GetPressureInfo() const {
447 return memory_quota_->GetPressureInfo();
448 }
449
GetFreeBytes()450 size_t GetFreeBytes() const {
451 return free_bytes_.load(std::memory_order_relaxed);
452 }
453
IncrementShardIndex()454 size_t IncrementShardIndex() {
455 return chosen_shard_idx_.fetch_add(1, std::memory_order_relaxed);
456 }
457
458 private:
459 static constexpr size_t kMaxQuotaBufferSize = 1024 * 1024;
460
461 // Primitive reservation function.
462 GRPC_MUST_USE_RESULT absl::optional<size_t> TryReserve(MemoryRequest request);
463 // This function may be invoked during a memory release operation.
464 // It will try to return half of our free pool to the quota.
465 void MaybeDonateBack();
466 // Replenish bytes from the quota, without blocking, possibly entering
467 // overcommit.
468 void Replenish();
469 template <typename F>
InsertReclaimer(size_t pass,F fn)470 void InsertReclaimer(size_t pass, F fn)
471 ABSL_EXCLUSIVE_LOCKS_REQUIRED(reclaimer_mu_) {
472 reclamation_handles_[pass] =
473 memory_quota_->reclaimer_queue(pass)->Insert(std::move(fn));
474 }
475
476 // Backing resource quota.
477 const std::shared_ptr<BasicMemoryQuota> memory_quota_;
478 // Amount of memory this allocator has cached for its own use: to avoid quota
479 // contention, each MemoryAllocator can keep some memory in addition to what
480 // it is immediately using, and the quota can pull it back under memory
481 // pressure.
482 std::atomic<size_t> free_bytes_{0};
483 // Amount of memory taken from the quota by this allocator.
484 std::atomic<size_t> taken_bytes_{sizeof(GrpcMemoryAllocatorImpl)};
485 // Index used to randomly choose shard to return bytes from.
486 std::atomic<size_t> chosen_shard_idx_{0};
487 // We try to donate back some memory periodically to the central quota.
488 PeriodicUpdate donate_back_{Duration::Seconds(10)};
489 Mutex reclaimer_mu_;
490 bool shutdown_ ABSL_GUARDED_BY(reclaimer_mu_) = false;
491 // Indices into the various reclaimer queues, used so that we can cancel
492 // reclamation should we shutdown or get rebound.
493 OrphanablePtr<ReclaimerQueue::Handle>
494 reclamation_handles_[kNumReclamationPasses] ABSL_GUARDED_BY(
495 reclaimer_mu_);
496 };
497
498 // MemoryOwner is an enhanced MemoryAllocator that can also reclaim memory, and
499 // be rebound to a different memory quota.
500 // Different modules should not share a MemoryOwner between themselves, instead
501 // each module that requires a MemoryOwner should create one from a resource
502 // quota. This is because the MemoryOwner reclaimers are tied to the
503 // MemoryOwner's lifetime, and are not queryable, so passing a MemoryOwner to a
504 // new owning module means that module cannot reason about which reclaimers are
505 // active, nor what they might do.
506 class MemoryOwner final : public MemoryAllocator {
507 public:
508 MemoryOwner() = default;
509
MemoryOwner(std::shared_ptr<GrpcMemoryAllocatorImpl> allocator)510 explicit MemoryOwner(std::shared_ptr<GrpcMemoryAllocatorImpl> allocator)
511 : MemoryAllocator(std::move(allocator)) {}
512
513 // Post a reclaimer for some reclamation pass.
514 template <typename F>
PostReclaimer(ReclamationPass pass,F fn)515 void PostReclaimer(ReclamationPass pass, F fn) {
516 impl()->PostReclaimer(pass, std::move(fn));
517 }
518
519 // Instantaneous memory pressure in the underlying quota.
GetPressureInfo()520 BasicMemoryQuota::PressureInfo GetPressureInfo() const {
521 if (!is_valid()) return {};
522 return impl()->GetPressureInfo();
523 }
524
525 template <typename T, typename... Args>
MakeOrphanable(Args &&...args)526 OrphanablePtr<T> MakeOrphanable(Args&&... args) {
527 return OrphanablePtr<T>(New<T>(std::forward<Args>(args)...));
528 }
529
530 // Is this object valid (ie has not been moved out of or reset)
is_valid()531 bool is_valid() const { return impl() != nullptr; }
532
memory_pressure_high_threshold()533 static double memory_pressure_high_threshold() { return 0.99; }
534
535 // Return true if the controlled memory pressure is high.
IsMemoryPressureHigh()536 bool IsMemoryPressureHigh() const {
537 return GetPressureInfo().pressure_control_value >
538 memory_pressure_high_threshold();
539 }
540
541 private:
impl()542 const GrpcMemoryAllocatorImpl* impl() const {
543 return static_cast<const GrpcMemoryAllocatorImpl*>(get_internal_impl_ptr());
544 }
545
impl()546 GrpcMemoryAllocatorImpl* impl() {
547 return static_cast<GrpcMemoryAllocatorImpl*>(get_internal_impl_ptr());
548 }
549 };
550
551 // MemoryQuota tracks the amount of memory available as part of a ResourceQuota.
552 class MemoryQuota final
553 : public grpc_event_engine::experimental::MemoryAllocatorFactory {
554 public:
MemoryQuota(std::string name)555 explicit MemoryQuota(std::string name)
556 : memory_quota_(std::make_shared<BasicMemoryQuota>(std::move(name))) {
557 memory_quota_->Start();
558 }
~MemoryQuota()559 ~MemoryQuota() override {
560 if (memory_quota_ != nullptr) memory_quota_->Stop();
561 }
562
563 MemoryQuota(const MemoryQuota&) = delete;
564 MemoryQuota& operator=(const MemoryQuota&) = delete;
565 MemoryQuota(MemoryQuota&&) = default;
566 MemoryQuota& operator=(MemoryQuota&&) = default;
567
568 MemoryAllocator CreateMemoryAllocator(absl::string_view name) override;
569 MemoryOwner CreateMemoryOwner();
570
571 // Resize the quota to new_size.
SetSize(size_t new_size)572 void SetSize(size_t new_size) { memory_quota_->SetSize(new_size); }
573
IsMemoryPressureHigh()574 bool IsMemoryPressureHigh() const {
575 return memory_quota_->GetPressureInfo().pressure_control_value >
576 MemoryOwner::memory_pressure_high_threshold();
577 }
578
579 private:
580 friend class MemoryOwner;
581 std::shared_ptr<BasicMemoryQuota> memory_quota_;
582 };
583
584 using MemoryQuotaRefPtr = std::shared_ptr<MemoryQuota>;
MakeMemoryQuota(std::string name)585 inline MemoryQuotaRefPtr MakeMemoryQuota(std::string name) {
586 return std::make_shared<MemoryQuota>(std::move(name));
587 }
588
589 std::vector<std::shared_ptr<BasicMemoryQuota>> AllMemoryQuotas();
590
591 void SetContainerMemoryPressure(double pressure);
592
593 double ContainerMemoryPressure();
594
595 } // namespace grpc_core
596
597 #endif // GRPC_SRC_CORE_LIB_RESOURCE_QUOTA_MEMORY_QUOTA_H
598