1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
content / browser / cache_storage / cache_storage_scheduler.cc [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/cache_storage/cache_storage_scheduler.h"
#include <string>
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/not_fatal_until.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "content/browser/cache_storage/cache_storage_histogram_utils.h"
#include "content/browser/cache_storage/cache_storage_operation.h"
namespace content {
namespace {
// Maximum parallel shared operations. This constant was selected via
// experimentation. We tried 4, 16, and 64 for the limit. 16 was clearly
// better than 4, but 64 was did not provide significant further benefit.
constexpr int kDefaultMaxSharedOps = 16;
const base::FeatureParam<int> kCacheStorageMaxSharedOps{
&kCacheStorageParallelOps, "max_shared_ops", kDefaultMaxSharedOps};
bool OpPointerLessThan(const std::unique_ptr<CacheStorageOperation>& left,
const std::unique_ptr<CacheStorageOperation>& right) {
DCHECK(left);
DCHECK(right);
// We want to prioritize high priority operations, but otherwise sort
// by creation order. Since the first created operations will have a lower
// identifier value we reverse the logic of the id comparison.
//
// Note, there might be a slight mis-ordering when the 64-bit id values
// rollover, but this should not be critical and will happen very rarely.
if (left->priority() < right->priority()) {
return true;
}
if (left->priority() > right->priority()) {
return false;
}
return left->id() > right->id();
}
} // namespace
// Enables support for parallel cache_storage operations via the
// "max_shared_ops" fieldtrial parameter.
BASE_FEATURE(kCacheStorageParallelOps,
"CacheStorageParallelOps",
base::FEATURE_ENABLED_BY_DEFAULT);
CacheStorageScheduler::CacheStorageScheduler(
CacheStorageSchedulerClient client_type,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: task_runner_(std::move(task_runner)), client_type_(client_type) {
std::make_heap(pending_operations_.begin(), pending_operations_.end(),
&OpPointerLessThan);
}
CacheStorageScheduler::~CacheStorageScheduler() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
CacheStorageSchedulerId CacheStorageScheduler::CreateId() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return next_id_++;
}
void CacheStorageScheduler::ScheduleOperation(
CacheStorageSchedulerId id,
CacheStorageSchedulerMode mode,
CacheStorageSchedulerOp op_type,
CacheStorageSchedulerPriority priority,
base::OnceClosure closure) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
RecordCacheStorageSchedulerUMA(CacheStorageSchedulerUMA::kQueueLength,
client_type_, op_type,
pending_operations_.size());
pending_operations_.push_back(std::make_unique<CacheStorageOperation>(
std::move(closure), id, client_type_, mode, op_type, priority,
task_runner_));
std::push_heap(pending_operations_.begin(), pending_operations_.end(),
&OpPointerLessThan);
MaybeRunOperation();
}
void CacheStorageScheduler::CompleteOperationAndRunNext(
CacheStorageSchedulerId id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = running_operations_.find(id);
CHECK(it != running_operations_.end(), base::NotFatalUntil::M130);
DCHECK_EQ(it->second->id(), id);
if (it->second->mode() == CacheStorageSchedulerMode::kShared) {
DCHECK_EQ(num_running_exclusive_, 0);
DCHECK_GT(num_running_shared_, 0);
num_running_shared_ -= 1;
} else {
DCHECK_EQ(num_running_shared_, 0);
DCHECK_EQ(num_running_exclusive_, 1);
num_running_exclusive_ -= 1;
}
running_operations_.erase(it);
MaybeRunOperation();
}
bool CacheStorageScheduler::ScheduledOperations() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !running_operations_.empty() || !pending_operations_.empty();
}
bool CacheStorageScheduler::IsRunningExclusiveOperation() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return num_running_exclusive_ > 0;
}
void CacheStorageScheduler::DispatchOperationTask(base::OnceClosure task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::move(task).Run();
}
void CacheStorageScheduler::MaybeRunOperation() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Most operations are wrapped with `CompleteOperationAndRunNext()`, which
// means that executing an operation can cause re-entrancy in
// `MaybeRunOperation().` No-op in this case; the next operation will be run
// in the next loop iteration. Note that this doesn't use `AutoReset` because
// running an operation can cause `this` to be deleted. See
// https://crbug.com/370069678.
if (in_maybe_run_) {
return;
}
in_maybe_run_ = true;
base::WeakPtr<CacheStorageScheduler> this_ptr =
weak_ptr_factory_.GetWeakPtr();
base::ScopedClosureRunner reset(base::BindOnce(
[](base::WeakPtr<CacheStorageScheduler> scheduler) {
if (scheduler) {
scheduler->in_maybe_run_ = false;
}
},
this_ptr));
while (this_ptr && !pending_operations_.empty()) {
base::WeakPtr<CacheStorageOperation> next_operation =
pending_operations_.front()->AsWeakPtr();
// Determine if we can run the next operation based on its mode
// and the current state of executing operations. We allow multiple
// kShared operations to run in parallel, but a kExclusive operation
// must not overlap with any other operation.
if (num_running_exclusive_ > 0) {
return;
}
const bool is_shared_op =
next_operation->mode() == CacheStorageSchedulerMode::kShared;
const int max_concurrent_ops =
is_shared_op ? kCacheStorageMaxSharedOps.Get() : 1;
if (num_running_shared_ >= max_concurrent_ops) {
return;
}
running_operations_.emplace(next_operation->id(),
std::move(pending_operations_.front()));
std::pop_heap(pending_operations_.begin(), pending_operations_.end(),
&OpPointerLessThan);
pending_operations_.pop_back();
RecordCacheStorageSchedulerUMA(
CacheStorageSchedulerUMA::kQueueDuration, client_type_,
next_operation->op_type(),
base::TimeTicks::Now() - next_operation->creation_ticks());
if (is_shared_op) {
CHECK_EQ(num_running_exclusive_, 0);
num_running_shared_ += 1;
} else {
CHECK_EQ(num_running_exclusive_, 0);
CHECK_EQ(num_running_shared_, 0);
num_running_exclusive_ += 1;
}
DispatchOperationTask(
base::BindOnce(&CacheStorageOperation::Run, next_operation));
// `next_operation` and `this_ptr` may both be null at this point.
}
}
} // namespace content