| // Copyright 2021 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "missive/resources/resource_manager.h" | |
| #include <atomic> | |
| #include <cstdint> | |
| #include <optional> | |
| #include <utility> | |
| #include <base/logging.h> | |
| #include <base/memory/ref_counted.h> | |
| #include <base/memory/scoped_refptr.h> | |
| #include <base/sequence_checker.h> | |
| #include <base/task/bind_post_task.h> | |
| #include <base/task/sequenced_task_runner.h> | |
| #include <base/task/task_traits.h> | |
| #include <base/task/thread_pool.h> | |
| namespace reporting { | |
| ResourceManager::ResourceManager(uint64_t total_size) | |
| : total_(total_size), | |
| sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( | |
| {base::TaskPriority::BEST_EFFORT})) { | |
| DETACH_FROM_SEQUENCE(sequence_checker_); | |
| } | |
| ResourceManager::~ResourceManager() = default; | |
| bool ResourceManager::Reserve(uint64_t size) { | |
| uint64_t old_used = used_.fetch_add(size); | |
| if (old_used + size > total_) { | |
| used_.fetch_sub(size); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void ResourceManager::Discard(uint64_t size) { | |
| CHECK_LE(size, used_.load()); | |
| used_.fetch_sub(size); | |
| sequenced_task_runner_->PostTask( | |
| FROM_HERE, base::BindOnce(&ResourceManager::FlushCallbacks, | |
| base::WrapRefCounted(this))); | |
| } | |
| uint64_t ResourceManager::GetTotal() const { | |
| return total_; | |
| } | |
| uint64_t ResourceManager::GetUsed() const { | |
| return used_.load(); | |
| } | |
| void ResourceManager::Test_SetTotal(uint64_t test_total) { | |
| total_ = test_total; | |
| } | |
| void ResourceManager::RegisterCallback(uint64_t size, base::OnceClosure cb) { | |
| sequenced_task_runner_->PostTask( | |
| FROM_HERE, | |
| base::BindOnce( | |
| [](scoped_refptr<ResourceManager> self, uint64_t size, | |
| base::OnceClosure cb) { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_); | |
| self->resource_callbacks_.emplace(size, std::move(cb)); | |
| // Attempt to apply remaining callbacks | |
| // (this is especially important if the new callback is registered | |
| // with no allocations to be released - we don't want the callback | |
| // to wait indefinitely in this case). | |
| self->FlushCallbacks(); | |
| }, | |
| base::WrapRefCounted(this), size, | |
| base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(), | |
| std::move(cb)))); | |
| } | |
| void ResourceManager::FlushCallbacks() { | |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | |
| int64_t remained = GetTotal() - GetUsed(); // No synchronization whatsoever. | |
| while (remained > 0 && !resource_callbacks_.empty()) { | |
| auto& cb_pair = resource_callbacks_.front(); | |
| if (cb_pair.first > static_cast<uint64_t>(remained)) { | |
| break; | |
| } | |
| remained -= cb_pair.first; | |
| std::move(cb_pair.second).Run(); | |
| resource_callbacks_.pop(); | |
| } | |
| } | |
| ScopedReservation::ScopedReservation() noexcept = default; | |
| ScopedReservation::ScopedReservation( | |
| uint64_t size, scoped_refptr<ResourceManager> resource_manager) noexcept | |
| : resource_manager_(resource_manager) { | |
| if (size == 0uL || !resource_manager->Reserve(size)) { | |
| return; | |
| } | |
| size_ = size; | |
| } | |
| ScopedReservation::ScopedReservation( | |
| uint64_t size, const ScopedReservation& other_reservation) noexcept | |
| : resource_manager_(other_reservation.resource_manager_) { | |
| if (size == 0uL || !resource_manager_.get() || | |
| !resource_manager_->Reserve(size)) { | |
| return; | |
| } | |
| size_ = size; | |
| } | |
| ScopedReservation::ScopedReservation(ScopedReservation&& other) noexcept | |
| : resource_manager_(other.resource_manager_), | |
| size_(std::exchange(other.size_, std::nullopt)) {} | |
| bool ScopedReservation::reserved() const { | |
| return size_.has_value(); | |
| } | |
| bool ScopedReservation::Reduce(uint64_t new_size) { | |
| if (!reserved()) { | |
| return false; | |
| } | |
| if (new_size < 0 || size_.value() < new_size) { | |
| return false; | |
| } | |
| resource_manager_->Discard(size_.value() - new_size); | |
| if (new_size > 0) { | |
| size_ = new_size; | |
| } else { | |
| size_ = std::nullopt; | |
| } | |
| return true; | |
| } | |
| void ScopedReservation::HandOver(ScopedReservation& other) { | |
| if (resource_manager_.get()) { | |
| CHECK_EQ(resource_manager_.get(), other.resource_manager_.get()) | |
| << "Reservations are not related"; | |
| } else { | |
| CHECK(!reserved()) << "Unattached reservation may not have size"; | |
| resource_manager_ = other.resource_manager_; | |
| } | |
| if (!other.reserved()) { | |
| return; // Nothing changes. | |
| } | |
| const uint64_t old_size = (reserved() ? size_.value() : 0uL); | |
| size_ = old_size + std::exchange(other.size_, std::nullopt).value(); | |
| } | |
| ScopedReservation::~ScopedReservation() { | |
| if (reserved()) { | |
| resource_manager_->Discard(size_.value()); | |
| } | |
| } | |
| } // namespace reporting |