• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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