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