• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The 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 #ifndef GRPC_EVENT_ENGINE_MEMORY_ALLOCATOR_H
15 #define GRPC_EVENT_ENGINE_MEMORY_ALLOCATOR_H
16 
17 #include <grpc/event_engine/internal/memory_allocator_impl.h>
18 #include <grpc/slice.h>
19 #include <grpc/support/port_platform.h>
20 #include <stdlib.h>  // for abort()
21 
22 #include <algorithm>
23 #include <memory>
24 #include <type_traits>
25 #include <vector>
26 
27 namespace grpc_event_engine {
28 namespace experimental {
29 
30 // Tracks memory allocated by one system.
31 // Is effectively a thin wrapper/smart pointer for a MemoryAllocatorImpl,
32 // providing a convenient and stable API.
33 class MemoryAllocator {
34  public:
35   /// Construct a MemoryAllocator given an internal::MemoryAllocatorImpl
36   /// implementation. The constructed MemoryAllocator will call
37   /// MemoryAllocatorImpl::Shutdown() upon destruction.
MemoryAllocator(std::shared_ptr<internal::MemoryAllocatorImpl> allocator)38   explicit MemoryAllocator(
39       std::shared_ptr<internal::MemoryAllocatorImpl> allocator)
40       : allocator_(std::move(allocator)) {}
41   // Construct an invalid MemoryAllocator.
MemoryAllocator()42   MemoryAllocator() : allocator_(nullptr) {}
~MemoryAllocator()43   ~MemoryAllocator() {
44     if (allocator_ != nullptr) allocator_->Shutdown();
45   }
46 
47   MemoryAllocator(const MemoryAllocator&) = delete;
48   MemoryAllocator& operator=(const MemoryAllocator&) = delete;
49 
50   MemoryAllocator(MemoryAllocator&&) = default;
51   MemoryAllocator& operator=(MemoryAllocator&&) = default;
52 
53   /// Drop the underlying allocator and make this an empty object.
54   /// The object will not be usable after this call unless it's a valid
55   /// allocator is moved into it.
Reset()56   void Reset() {
57     auto a = std::move(allocator_);
58     if (a != nullptr) a->Shutdown();
59   }
60 
61   /// Reserve bytes from the quota.
62   /// If we enter overcommit, reclamation will begin concurrently.
63   /// Returns the number of bytes reserved.
Reserve(MemoryRequest request)64   size_t Reserve(MemoryRequest request) { return allocator_->Reserve(request); }
65 
66   /// Release some bytes that were previously reserved.
Release(size_t n)67   void Release(size_t n) { return allocator_->Release(n); }
68 
69   //
70   // The remainder of this type are helper functions implemented in terms of
71   // Reserve/Release.
72   //
73 
74   /// An automatic releasing reservation of memory.
75   class Reservation {
76    public:
77     Reservation() = default;
78     Reservation(const Reservation&) = delete;
79     Reservation& operator=(const Reservation&) = delete;
80     Reservation(Reservation&&) = default;
81     Reservation& operator=(Reservation&&) = default;
~Reservation()82     ~Reservation() {
83       if (allocator_ != nullptr) allocator_->Release(size_);
84     }
85 
86    private:
87     friend class MemoryAllocator;
Reservation(std::shared_ptr<internal::MemoryAllocatorImpl> allocator,size_t size)88     Reservation(std::shared_ptr<internal::MemoryAllocatorImpl> allocator,
89                 size_t size)
90         : allocator_(std::move(allocator)), size_(size) {}
91 
92     std::shared_ptr<internal::MemoryAllocatorImpl> allocator_;
93     size_t size_ = 0;
94   };
95 
96   /// Reserve bytes from the quota and automatically release them when
97   /// Reservation is destroyed.
MakeReservation(MemoryRequest request)98   Reservation MakeReservation(MemoryRequest request) {
99     return Reservation(allocator_, Reserve(request));
100   }
101 
102   /// Allocate a new object of type T, with constructor arguments.
103   /// The returned type is wrapped, and upon destruction the reserved memory
104   /// will be released to the allocator automatically. As such, T must have a
105   /// virtual destructor so we can insert the necessary hook.
106   template <typename T, typename... Args>
New(Args &&...args)107   typename std::enable_if<std::has_virtual_destructor<T>::value, T*>::type New(
108       Args&&... args) {
109     // Wrap T such that when it's destroyed, we can release memory back to the
110     // allocator.
111     class Wrapper final : public T {
112      public:
113       explicit Wrapper(std::shared_ptr<internal::MemoryAllocatorImpl> allocator,
114                        Args&&... args)
115           : T(std::forward<Args>(args)...), allocator_(std::move(allocator)) {}
116       ~Wrapper() override { allocator_->Release(sizeof(*this)); }
117 
118      private:
119       const std::shared_ptr<internal::MemoryAllocatorImpl> allocator_;
120     };
121     Reserve(sizeof(Wrapper));
122     return new Wrapper(allocator_, std::forward<Args>(args)...);
123   }
124 
125   /// Construct a unique_ptr immediately.
126   template <typename T, typename... Args>
MakeUnique(Args &&...args)127   std::unique_ptr<T> MakeUnique(Args&&... args) {
128     return std::unique_ptr<T>(New<T>(std::forward<Args>(args)...));
129   }
130 
131   /// Allocate a slice, using MemoryRequest to size the number of returned
132   /// bytes. For a variable length request, check the returned slice length to
133   /// verify how much memory was allocated. Takes care of reserving memory for
134   /// any relevant control structures also.
MakeSlice(MemoryRequest request)135   grpc_slice MakeSlice(MemoryRequest request) {
136     return allocator_->MakeSlice(request);
137   }
138 
139   /// A C++ allocator for containers of T.
140   template <typename T>
141   class Container {
142    public:
143     using value_type = T;
144 
145     /// Construct the allocator: \a underlying_allocator is borrowed, and must
146     /// outlive this object.
Container(MemoryAllocator * underlying_allocator)147     explicit Container(MemoryAllocator* underlying_allocator)
148         : underlying_allocator_(underlying_allocator) {}
149     template <typename U>
Container(const Container<U> & other)150     explicit Container(const Container<U>& other)
151         : underlying_allocator_(other.underlying_allocator()) {}
152 
underlying_allocator()153     MemoryAllocator* underlying_allocator() const {
154       return underlying_allocator_;
155     }
156 
allocate(size_t n)157     T* allocate(size_t n) {
158       underlying_allocator_->Reserve(n * sizeof(T));
159       return static_cast<T*>(::operator new(n * sizeof(T)));
160     }
deallocate(T * p,size_t n)161     void deallocate(T* p, size_t n) {
162       ::operator delete(p);
163       underlying_allocator_->Release(n * sizeof(T));
164     }
165 
166    private:
167     MemoryAllocator* underlying_allocator_;
168   };
169 
170  protected:
171   /// Return a pointer to the underlying implementation.
172   /// Note that the interface of said implementation is unstable and likely to
173   /// change at any time.
get_internal_impl_ptr()174   internal::MemoryAllocatorImpl* get_internal_impl_ptr() {
175     return allocator_.get();
176   }
177 
get_internal_impl_ptr()178   const internal::MemoryAllocatorImpl* get_internal_impl_ptr() const {
179     return allocator_.get();
180   }
181 
182  private:
183   std::shared_ptr<internal::MemoryAllocatorImpl> allocator_;
184 };
185 
186 // Wrapper type around std::vector to make initialization against a
187 // MemoryAllocator based container allocator easy.
188 template <typename T>
189 class Vector : public std::vector<T, MemoryAllocator::Container<T>> {
190  public:
Vector(MemoryAllocator * allocator)191   explicit Vector(MemoryAllocator* allocator)
192       : std::vector<T, MemoryAllocator::Container<T>>(
193             MemoryAllocator::Container<T>(allocator)) {}
194 };
195 
196 class MemoryAllocatorFactory {
197  public:
198   virtual ~MemoryAllocatorFactory() = default;
199   /// On Endpoint creation, call \a CreateMemoryAllocator to create a new
200   /// allocator for the endpoint.
201   /// \a name is used to label the memory allocator in debug logs.
202   /// Typically we'll want to:
203   ///    auto allocator = factory->CreateMemoryAllocator(peer_address_string);
204   ///    auto* endpoint = allocator->New<MyEndpoint>(std::move(allocator), ...);
205   virtual MemoryAllocator CreateMemoryAllocator(absl::string_view name) = 0;
206 };
207 
208 }  // namespace experimental
209 }  // namespace grpc_event_engine
210 
211 #endif  // GRPC_EVENT_ENGINE_MEMORY_ALLOCATOR_H
212