blob: 50b0db4d09cefe762dd8e2e64096c3e70ea81e98 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/disk_cache/sql/exclusive_operation_coordinator.h"
#include <optional>
#include <utility>
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "base/timer/elapsed_timer.h"
#include "net/disk_cache/sql/cache_entry_key.h"
namespace disk_cache {
namespace {
// Wraps an operation to record its queuing time in a UMA histogram.
base::OnceCallback<
void(std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle>)>
WrapWithUmaQueuingTime(
base::OnceCallback<
void(std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle>)>
operation,
const std::string_view histogram_name) {
return base::BindOnce(
[](base::OnceCallback<void(
std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle>)>
operation,
const std::string_view histogram_name, base::ElapsedTimer timer,
std::unique_ptr<ExclusiveOperationCoordinator::OperationHandle>
handle) {
base::UmaHistogramMicrosecondsTimes(histogram_name, timer.Elapsed());
std::move(operation).Run(std::move(handle));
},
std::move(operation), histogram_name, base::ElapsedTimer());
}
} // namespace
ExclusiveOperationCoordinator::OperationHandle::OperationHandle(
base::PassKey<ExclusiveOperationCoordinator>,
base::WeakPtr<ExclusiveOperationCoordinator> coordinator,
std::optional<CacheEntryKey> key)
: coordinator_(std::move(coordinator)), key_(std::move(key)) {}
ExclusiveOperationCoordinator::OperationHandle::~OperationHandle() {
if (coordinator_) {
coordinator_->OnOperationFinished(key_);
}
}
ExclusiveOperationCoordinator::ExclusiveOperationCoordinator() = default;
ExclusiveOperationCoordinator::~ExclusiveOperationCoordinator() = default;
void ExclusiveOperationCoordinator::PostOrRunExclusiveOperation(
OperationCallback operation) {
CHECK(operation);
operation = WrapWithUmaQueuingTime(
std::move(operation), "Net.SqlDiskCache.ExclusiveOperationDelay");
queue_.emplace(std::move(operation));
TryToRunNextOperation(std::nullopt);
}
void ExclusiveOperationCoordinator::PostOrRunNormalOperation(
const CacheEntryKey& key,
OperationCallback operation) {
CHECK(operation);
operation = WrapWithUmaQueuingTime(std::move(operation),
"Net.SqlDiskCache.NormalOperationDelay");
// If there is no queue, or the back of the queue is an exclusive operation,
// add a new `NormalOperationsQueueMap` to the back of the queue.
if (queue_.empty() ||
std::holds_alternative<ExclusiveOperation>(queue_.back())) {
queue_.push(NormalOperationsQueueMap());
}
// Add the callback to the queue for the given `key`.
// Normal operations with the same key are serialized.
std::get<NormalOperationsQueueMap>(queue_.back())[key].push(
std::move(operation));
TryToRunNextOperation(key);
}
void ExclusiveOperationCoordinator::OnOperationFinished(
const std::optional<CacheEntryKey>& key) {
CHECK(!queue_.empty());
// Verify that the front of the queue is a `NormalOperationsQueueMap` iff a
// `key` was provided.
CHECK_EQ(std::holds_alternative<NormalOperationsQueueMap>(queue_.front()),
key.has_value());
if (key.has_value()) {
// The operation that just finished was a normal operation.
// Get a reference to the `NormalOperationsQueueMap` at the front of the
// queue.
NormalOperationsQueueMap& normal_operations_map =
std::get<NormalOperationsQueueMap>(queue_.front());
auto it = normal_operations_map.find(key.value());
// The `NormalOperationsQueueMap` at the front of `queue_` must have a
// `base::queue<OperationCallback>` corresponding to `key`,
CHECK(it != normal_operations_map.end());
// and that `base::queue<OperationCallback>` must not be empty,
CHECK(!it->second.empty());
// and the OperationCallback at the front must be a null callback.
CHECK(it->second.front().is_null());
// Remove the OperationCallback that just finished running.
it->second.pop();
if (it->second.empty()) {
// There are no more operations for this key, so remove the key from the
// map.
normal_operations_map.erase(it);
if (normal_operations_map.empty()) {
// There are no more operations in the map, so remove the map from the
// queue. This phase has completed.
queue_.pop();
}
}
} else {
// The operation that just finished was an exclusive operation.
// Get a reference to the `ExclusiveOperation` at the front of the queue.
ExclusiveOperation& exclusive_operation =
std::get<ExclusiveOperation>(queue_.front());
// The ExclusiveOperation at the front of `queue_` must be a null callback.
CHECK(exclusive_operation.is_null());
// Remove the ExclusiveOperation from the queue. This phase has completed.
queue_.pop();
}
// The completion of an operation might allow the next one to start.
TryToRunNextOperation(key);
}
void ExclusiveOperationCoordinator::TryToRunNextOperation(
const std::optional<CacheEntryKey>& key) {
if (queue_.empty()) {
// Nothing to do.
return;
}
// A list of operations that can be run in this pass. We collect them first
// and run them later to avoid iterator invalidation issues caused by
// re-entrant calls if an operation completes synchronously.
std::vector<base::OnceClosure> runnable_ops;
if (std::holds_alternative<NormalOperationsQueueMap>(queue_.front())) {
// The next phase in the queue is a batch of normal operations.
// Get a reference to the `NormalOperationsQueueMap` at the front of the
// queue.
NormalOperationsQueueMap& normal_operations_map =
std::get<NormalOperationsQueueMap>(queue_.front());
// If a `key` was provided, attempt to run the next operation for that
// `key`. Otherwise, attempt to run operations for all keys.
if (key.has_value()) {
if (auto it = normal_operations_map.find(key.value());
it != normal_operations_map.end()) {
CHECK(!it->second.empty());
MaybeTakeAndResetPendingOperation(it->second.front(), key,
runnable_ops);
}
} else {
// Attempt to run one operation for each key in the map.
for (auto& pair : normal_operations_map) {
CHECK(!pair.second.empty());
MaybeTakeAndResetPendingOperation(pair.second.front(), pair.first,
runnable_ops);
}
}
} else {
// The next phase in the queue is an exclusive operation.
MaybeTakeAndResetPendingOperation(
std::get<ExclusiveOperation>(queue_.front()), std::nullopt,
runnable_ops);
}
// Run the collected operations.
for (auto& runnable_op : runnable_ops) {
std::move(runnable_op).Run();
}
}
void ExclusiveOperationCoordinator::MaybeTakeAndResetPendingOperation(
ExclusiveOperationCoordinator::OperationCallback& operation,
const std::optional<CacheEntryKey>& key,
std::vector<base::OnceClosure>& runnable_ops) {
if (!operation) {
return;
}
runnable_ops.push_back(base::BindOnce(
std::move(operation), std::make_unique<OperationHandle>(
base::PassKey<ExclusiveOperationCoordinator>(),
weak_factory_.GetWeakPtr(), key)));
// Reset to a null callback to indicate that the operation is
// currently running.
operation.Reset();
}
} // namespace disk_cache