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