1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://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, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_multibuf/allocator.h"
16
17 #include "pw_assert/check.h"
18
19 namespace pw::multibuf {
20
Allocate(size_t size)21 std::optional<MultiBuf> MultiBufAllocator::Allocate(size_t size) {
22 return Allocate(size, size);
23 }
24
Allocate(size_t min_size,size_t desired_size)25 std::optional<MultiBuf> MultiBufAllocator::Allocate(size_t min_size,
26 size_t desired_size) {
27 pw::Result<MultiBuf> result = DoAllocate(min_size, desired_size, false);
28 if (result.ok()) {
29 return std::move(*result);
30 }
31 return std::nullopt;
32 }
33
AllocateContiguous(size_t size)34 std::optional<MultiBuf> MultiBufAllocator::AllocateContiguous(size_t size) {
35 return AllocateContiguous(size, size);
36 }
37
AllocateContiguous(size_t min_size,size_t desired_size)38 std::optional<MultiBuf> MultiBufAllocator::AllocateContiguous(
39 size_t min_size, size_t desired_size) {
40 pw::Result<MultiBuf> result = DoAllocate(min_size, desired_size, true);
41 if (result.ok()) {
42 return std::move(*result);
43 }
44 return std::nullopt;
45 }
46
AllocateAsync(size_t size)47 MultiBufAllocationFuture MultiBufAllocator::AllocateAsync(size_t size) {
48 return MultiBufAllocationFuture(*this, size, size, false);
49 }
AllocateAsync(size_t min_size,size_t desired_size)50 MultiBufAllocationFuture MultiBufAllocator::AllocateAsync(size_t min_size,
51 size_t desired_size) {
52 return MultiBufAllocationFuture(*this, min_size, desired_size, false);
53 }
AllocateContiguousAsync(size_t size)54 MultiBufAllocationFuture MultiBufAllocator::AllocateContiguousAsync(
55 size_t size) {
56 return MultiBufAllocationFuture(*this, size, size, true);
57 }
AllocateContiguousAsync(size_t min_size,size_t desired_size)58 MultiBufAllocationFuture MultiBufAllocator::AllocateContiguousAsync(
59 size_t min_size, size_t desired_size) {
60 return MultiBufAllocationFuture(*this, min_size, desired_size, true);
61 }
62
MoreMemoryAvailable(size_t size_available,size_t contiguous_size_available)63 void MultiBufAllocator::MoreMemoryAvailable(size_t size_available,
64 size_t contiguous_size_available)
65 // Disable lock safety analysis: the access to `next_` requires locking
66 // `waiter->allocator_->lock_`, but that's the same as `lock_` which we
67 // already hold.
68 PW_NO_LOCK_SAFETY_ANALYSIS {
69 std::lock_guard lock(lock_);
70 internal::AllocationWaiter* current = first_waiter_;
71 while (current != nullptr) {
72 PW_DCHECK(current->allocator_ == this);
73 if ((current->min_size_ <= contiguous_size_available) ||
74 (!current->needs_contiguous_ && current->min_size_ <= size_available)) {
75 std::move(current->waker_).Wake();
76 }
77 current = current->next_;
78 }
79 }
80
AddWaiter(internal::AllocationWaiter * waiter)81 void MultiBufAllocator::AddWaiter(internal::AllocationWaiter* waiter) {
82 std::lock_guard lock(lock_);
83 AddWaiterLocked(waiter);
84 }
85
AddWaiterLocked(internal::AllocationWaiter * waiter)86 void MultiBufAllocator::AddWaiterLocked(internal::AllocationWaiter* waiter)
87 // Disable lock safety analysis: the access to `next_` requires locking
88 // `waiter->allocator_->lock_`, but that's the same as `lock_` which we
89 // already hold.
90 PW_NO_LOCK_SAFETY_ANALYSIS {
91 PW_DCHECK(waiter->allocator_ == this);
92 PW_DCHECK(waiter->next_ == nullptr);
93 auto old_first = first_waiter_;
94 first_waiter_ = waiter;
95 waiter->next_ = old_first;
96 }
97
RemoveWaiter(internal::AllocationWaiter * waiter)98 void MultiBufAllocator::RemoveWaiter(internal::AllocationWaiter* waiter)
99 // Disable lock safety analysis: the access to `next_` requires locking
100 // `waiter->allocator_->lock_`, but that's the same as `lock_` which we
101 // already hold.
102 PW_NO_LOCK_SAFETY_ANALYSIS {
103 std::lock_guard lock(lock_);
104 RemoveWaiterLocked(waiter);
105 PW_DCHECK(waiter->next_ == nullptr);
106 }
107
RemoveWaiterLocked(internal::AllocationWaiter * waiter)108 void MultiBufAllocator::RemoveWaiterLocked(internal::AllocationWaiter* waiter)
109 // Disable lock safety analysis: the access to `next_` requires locking
110 // `waiter->allocator_->lock_`, but that's the same as `lock_` which we
111 // already hold.
112 PW_NO_LOCK_SAFETY_ANALYSIS {
113 PW_DCHECK(waiter->allocator_ == this);
114 if (waiter == first_waiter_) {
115 first_waiter_ = first_waiter_->next_;
116 waiter->next_ = nullptr;
117 return;
118 }
119 auto current = first_waiter_;
120 while (current != nullptr) {
121 if (current->next_ == waiter) {
122 current->next_ = waiter->next_;
123 waiter->next_ = nullptr;
124 return;
125 }
126 current = current->next_;
127 }
128 }
129
130 namespace internal {
131
AllocationWaiter(AllocationWaiter && other)132 AllocationWaiter::AllocationWaiter(AllocationWaiter&& other)
133 : allocator_(other.allocator_),
134 waker_(),
135 next_(nullptr),
136 min_size_(other.min_size_),
137 desired_size_(other.desired_size_),
138 needs_contiguous_(other.needs_contiguous_) {
139 std::lock_guard lock(allocator_->lock_);
140 allocator_->RemoveWaiterLocked(&other);
141 allocator_->AddWaiterLocked(this);
142 // We must move the waker under the lock in order to ensure that there is no
143 // race between swapping ``AllocationWaiter``s and the waker being awoken by
144 // the allocator.
145 waker_ = std::move(other.waker_);
146 }
147
operator =(AllocationWaiter && other)148 AllocationWaiter& AllocationWaiter::operator=(AllocationWaiter&& other) {
149 allocator_->RemoveWaiter(this);
150
151 allocator_ = other.allocator_;
152 min_size_ = other.min_size_;
153 desired_size_ = other.desired_size_;
154 needs_contiguous_ = other.needs_contiguous_;
155
156 std::lock_guard lock(allocator_->lock_);
157 allocator_->RemoveWaiterLocked(&other);
158 allocator_->AddWaiterLocked(this);
159 // We must move the waker under the lock in order to ensure that there is no
160 // race between swapping ``AllocationWaiter``s and the waker being awoken by
161 // the allocator.
162 waker_ = std::move(other.waker_);
163
164 return *this;
165 }
166
~AllocationWaiter()167 AllocationWaiter::~AllocationWaiter() { allocator_->RemoveWaiter(this); }
168
169 } // namespace internal
170
Pend(async2::Context & cx)171 async2::Poll<std::optional<MultiBuf>> MultiBufAllocationFuture::Pend(
172 async2::Context& cx) {
173 bool should_attempt = waiter_.ShouldAttemptAllocate();
174 // We set the waker prior to attempting allocation because we want
175 // to receive notifications that may have raced with our attempt
176 // to allocate. If we wait to set until after attempting,
177 // we'd have to re-attempt in order to ensure that no new memory
178 // became available between our attempt and setting the waker.
179 waiter_.SetWaker(cx.GetWaker(async2::WaitReason::Unspecified()));
180 if (!should_attempt) {
181 return async2::Pending();
182 }
183 auto result = TryAllocate();
184 if (result.IsReady()) {
185 waiter_.ClearWaker();
186 }
187 return result;
188 }
189
TryAllocate()190 async2::Poll<std::optional<MultiBuf>> MultiBufAllocationFuture::TryAllocate() {
191 pw::Result<MultiBuf> buf_opt = waiter_.allocator().DoAllocate(
192 waiter_.min_size(), waiter_.desired_size(), waiter_.needs_contiguous());
193 if (buf_opt.ok()) {
194 return async2::Ready<std::optional<MultiBuf>>(std::move(*buf_opt));
195 }
196 if (buf_opt.status().IsOutOfRange()) {
197 return async2::Ready<std::optional<MultiBuf>>(std::nullopt);
198 }
199 return async2::Pending();
200 }
201
202 } // namespace pw::multibuf
203