blob: fc9d35fbc746e512045b9254f083aeff08e86baa [file] [log] [blame]
// Copyright 2024 The Pigweed 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
//
// https://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.
#include "pw_multibuf/allocator.h"
#include <mutex>
#include "pw_assert/check.h"
namespace pw::multibuf {
std::optional<MultiBuf> MultiBufAllocator::Allocate(size_t size) {
return Allocate(size, size);
}
std::optional<MultiBuf> MultiBufAllocator::Allocate(size_t min_size,
size_t desired_size) {
pw::Result<MultiBuf> result = DoAllocate(min_size, desired_size, false);
if (result.ok()) {
return std::move(*result);
}
return std::nullopt;
}
std::optional<MultiBuf> MultiBufAllocator::AllocateContiguous(size_t size) {
return AllocateContiguous(size, size);
}
std::optional<MultiBuf> MultiBufAllocator::AllocateContiguous(
size_t min_size, size_t desired_size) {
pw::Result<MultiBuf> result = DoAllocate(min_size, desired_size, true);
if (result.ok()) {
return std::move(*result);
}
return std::nullopt;
}
MultiBufAllocationFuture MultiBufAllocator::AllocateAsync(size_t size) {
return MultiBufAllocationFuture(*this, size, size, false);
}
MultiBufAllocationFuture MultiBufAllocator::AllocateAsync(size_t min_size,
size_t desired_size) {
return MultiBufAllocationFuture(*this, min_size, desired_size, false);
}
MultiBufAllocationFuture MultiBufAllocator::AllocateContiguousAsync(
size_t size) {
return MultiBufAllocationFuture(*this, size, size, true);
}
MultiBufAllocationFuture MultiBufAllocator::AllocateContiguousAsync(
size_t min_size, size_t desired_size) {
return MultiBufAllocationFuture(*this, min_size, desired_size, true);
}
void MultiBufAllocator::MoreMemoryAvailable(size_t size_available,
size_t contiguous_size_available)
// Disable lock safety analysis: the access to `next_` requires locking
// `waiter->allocator_->lock_`, but that's the same as `lock_` which we
// already hold.
PW_NO_LOCK_SAFETY_ANALYSIS {
std::lock_guard lock(lock_);
internal::AllocationWaiter* current = first_waiter_;
while (current != nullptr) {
PW_DCHECK(current->allocator_ == this);
if ((current->min_size_ <= contiguous_size_available) ||
(!current->needs_contiguous_ && current->min_size_ <= size_available)) {
std::move(current->waker_).Wake();
}
current = current->next_;
}
}
void MultiBufAllocator::AddWaiter(internal::AllocationWaiter* waiter) {
std::lock_guard lock(lock_);
AddWaiterLocked(waiter);
}
void MultiBufAllocator::AddWaiterLocked(internal::AllocationWaiter* waiter)
// Disable lock safety analysis: the access to `next_` requires locking
// `waiter->allocator_->lock_`, but that's the same as `lock_` which we
// already hold.
PW_NO_LOCK_SAFETY_ANALYSIS {
PW_DCHECK(waiter->allocator_ == this);
PW_DCHECK(waiter->next_ == nullptr);
auto old_first = first_waiter_;
first_waiter_ = waiter;
waiter->next_ = old_first;
}
void MultiBufAllocator::RemoveWaiter(internal::AllocationWaiter* waiter)
// Disable lock safety analysis: the access to `next_` requires locking
// `waiter->allocator_->lock_`, but that's the same as `lock_` which we
// already hold.
PW_NO_LOCK_SAFETY_ANALYSIS {
std::lock_guard lock(lock_);
RemoveWaiterLocked(waiter);
PW_DCHECK(waiter->next_ == nullptr);
}
void MultiBufAllocator::RemoveWaiterLocked(internal::AllocationWaiter* waiter)
// Disable lock safety analysis: the access to `next_` requires locking
// `waiter->allocator_->lock_`, but that's the same as `lock_` which we
// already hold.
PW_NO_LOCK_SAFETY_ANALYSIS {
PW_DCHECK(waiter->allocator_ == this);
if (waiter == first_waiter_) {
first_waiter_ = first_waiter_->next_;
waiter->next_ = nullptr;
return;
}
auto current = first_waiter_;
while (current != nullptr) {
if (current->next_ == waiter) {
current->next_ = waiter->next_;
waiter->next_ = nullptr;
return;
}
current = current->next_;
}
}
namespace internal {
AllocationWaiter::AllocationWaiter(AllocationWaiter&& other)
: allocator_(other.allocator_),
waker_(),
next_(nullptr),
min_size_(other.min_size_),
desired_size_(other.desired_size_),
needs_contiguous_(other.needs_contiguous_) {
std::lock_guard lock(allocator_->lock_);
allocator_->RemoveWaiterLocked(&other);
allocator_->AddWaiterLocked(this);
// We must move the waker under the lock in order to ensure that there is no
// race between swapping ``AllocationWaiter``s and the waker being awoken by
// the allocator.
waker_ = std::move(other.waker_);
}
AllocationWaiter& AllocationWaiter::operator=(AllocationWaiter&& other) {
allocator_->RemoveWaiter(this);
allocator_ = other.allocator_;
min_size_ = other.min_size_;
desired_size_ = other.desired_size_;
needs_contiguous_ = other.needs_contiguous_;
std::lock_guard lock(allocator_->lock_);
allocator_->RemoveWaiterLocked(&other);
allocator_->AddWaiterLocked(this);
// We must move the waker under the lock in order to ensure that there is no
// race between swapping ``AllocationWaiter``s and the waker being awoken by
// the allocator.
waker_ = std::move(other.waker_);
return *this;
}
AllocationWaiter::~AllocationWaiter() { allocator_->RemoveWaiter(this); }
} // namespace internal
async2::Poll<std::optional<MultiBuf>> MultiBufAllocationFuture::Pend(
async2::Context& cx) {
bool should_attempt = waiter_.ShouldAttemptAllocate();
// We set the waker prior to attempting allocation because we want
// to receive notifications that may have raced with our attempt
// to allocate. If we wait to set until after attempting,
// we'd have to re-attempt in order to ensure that no new memory
// became available between our attempt and setting the waker.
waiter_.SetWaker(cx.GetWaker(async2::WaitReason::Unspecified()));
if (!should_attempt) {
return async2::Pending();
}
auto result = TryAllocate();
if (result.IsReady()) {
waiter_.ClearWaker();
}
return result;
}
async2::Poll<std::optional<MultiBuf>> MultiBufAllocationFuture::TryAllocate() {
pw::Result<MultiBuf> buf_opt = waiter_.allocator().DoAllocate(
waiter_.min_size(), waiter_.desired_size(), waiter_.needs_contiguous());
if (buf_opt.ok()) {
return async2::Ready<std::optional<MultiBuf>>(std::move(*buf_opt));
}
if (buf_opt.status().IsOutOfRange()) {
return async2::Ready<std::optional<MultiBuf>>(std::nullopt);
}
return async2::Pending();
}
} // namespace pw::multibuf