1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/indexed_db/indexed_db_tombstone_sweeper.h"
6
7 #include <string>
8
9 #include "base/metrics/histogram_functions.h"
10 #include "base/rand_util.h"
11 #include "base/sequenced_task_runner.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/threading/sequenced_task_runner_handle.h"
14 #include "base/time/tick_clock.h"
15 #include "components/services/storage/indexed_db/scopes/varint_coding.h"
16 #include "content/browser/indexed_db/indexed_db_backing_store.h"
17 #include "third_party/blink/public/common/indexeddb/indexeddb_key.h"
18 #include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
19 #include "third_party/leveldatabase/env_chromium.h"
20 #include "third_party/leveldatabase/src/include/leveldb/db.h"
21 #include "third_party/leveldatabase/src/include/leveldb/iterator.h"
22
23 namespace content {
24 namespace {
25
26 using StopReason = IndexedDBPreCloseTaskQueue::StopReason;
27 using blink::IndexedDBDatabaseMetadata;
28 using blink::IndexedDBIndexMetadata;
29 using blink::IndexedDBKey;
30 using blink::IndexedDBObjectStoreMetadata;
31
32 } // namespace
33
34 template <typename T>
WrappingIterator()35 WrappingIterator<T>::WrappingIterator() {}
36
37 template <typename T>
WrappingIterator(const T * container,size_t start_position)38 WrappingIterator<T>::WrappingIterator(const T* container,
39 size_t start_position) {
40 container_ = container;
41 valid_ = true;
42 iterations_done_ = 0;
43 DCHECK_LT(start_position, container_->size());
44 inner_ = container_->begin();
45 std::advance(inner_, start_position);
46 DCHECK(inner_ != container_->end());
47 }
48
49 template <typename T>
~WrappingIterator()50 WrappingIterator<T>::~WrappingIterator() {}
51
52 template <typename T>
Next()53 void WrappingIterator<T>::Next() {
54 DCHECK(valid_);
55 iterations_done_++;
56 if (iterations_done_ >= container_->size()) {
57 valid_ = false;
58 return;
59 }
60 inner_++;
61 if (inner_ == container_->end()) {
62 inner_ = container_->begin();
63 }
64 }
65
66 template <typename T>
Value() const67 const typename T::value_type& WrappingIterator<T>::Value() const {
68 CHECK(valid_);
69 return *inner_;
70 }
71
IndexedDBTombstoneSweeper(int round_iterations,int max_iterations,leveldb::DB * database)72 IndexedDBTombstoneSweeper::IndexedDBTombstoneSweeper(int round_iterations,
73 int max_iterations,
74 leveldb::DB* database)
75 : IndexedDBPreCloseTaskQueue::PreCloseTask(database),
76 max_round_iterations_(round_iterations),
77 max_iterations_(max_iterations) {
78 sweep_state_.start_database_seed = static_cast<size_t>(base::RandUint64());
79 sweep_state_.start_object_store_seed =
80 static_cast<size_t>(base::RandUint64());
81 sweep_state_.start_index_seed = static_cast<size_t>(base::RandUint64());
82 }
83
~IndexedDBTombstoneSweeper()84 IndexedDBTombstoneSweeper::~IndexedDBTombstoneSweeper() {}
85
RequiresMetadata() const86 bool IndexedDBTombstoneSweeper::RequiresMetadata() const {
87 return true;
88 }
89
SetMetadata(const std::vector<IndexedDBDatabaseMetadata> * metadata)90 void IndexedDBTombstoneSweeper::SetMetadata(
91 const std::vector<IndexedDBDatabaseMetadata>* metadata) {
92 database_metadata_ = metadata;
93 total_indices_ = 0;
94 for (const auto& db : *metadata) {
95 for (const auto& os_pair : db.object_stores) {
96 total_indices_ += os_pair.second.indexes.size();
97 }
98 }
99 }
100
101 IndexedDBTombstoneSweeper::SweepState::SweepState() = default;
102
103 IndexedDBTombstoneSweeper::SweepState::~SweepState() = default;
104
Stop(StopReason reason)105 void IndexedDBTombstoneSweeper::Stop(StopReason reason) {
106 leveldb::Status s;
107 RecordUMAStats(reason, base::nullopt, s);
108 }
109
RunRound()110 bool IndexedDBTombstoneSweeper::RunRound() {
111 DCHECK(database_metadata_);
112
113 if (database_metadata_->empty())
114 return true;
115
116 if (!start_time_) {
117 start_time_ = clock_for_testing_ ? clock_for_testing_->NowTicks()
118 : base::TimeTicks::Now();
119 }
120
121 leveldb::Status s;
122 Status status = DoSweep(&s);
123
124 if (status != Status::DONE_ERROR) {
125 s = FlushDeletions();
126 if (!s.ok())
127 status = Status::DONE_ERROR;
128 }
129
130 if (status == Status::SWEEPING)
131 return false;
132
133 RecordUMAStats(base::nullopt, status, s);
134 return true;
135 }
136
RecordUMAStats(base::Optional<StopReason> stop_reason,base::Optional<IndexedDBTombstoneSweeper::Status> status,const leveldb::Status & leveldb_error)137 void IndexedDBTombstoneSweeper::RecordUMAStats(
138 base::Optional<StopReason> stop_reason,
139 base::Optional<IndexedDBTombstoneSweeper::Status> status,
140 const leveldb::Status& leveldb_error) {
141 DCHECK(stop_reason || status);
142 DCHECK(!stop_reason || !status);
143
144 // Metadata error statistics are recorded in the PreCloseTaskList.
145 if (stop_reason && stop_reason == StopReason::METADATA_ERROR)
146 return;
147
148 std::string uma_count_label =
149 "WebCore.IndexedDB.TombstoneSweeper.NumDeletedTombstones.";
150 std::string uma_size_label =
151 "WebCore.IndexedDB.TombstoneSweeper.DeletedTombstonesSize.";
152
153 if (stop_reason) {
154 switch (stop_reason.value()) {
155 case StopReason::NEW_CONNECTION:
156 uma_count_label.append("ConnectionOpened");
157 uma_size_label.append("ConnectionOpened");
158 break;
159 case StopReason::TIMEOUT:
160 uma_count_label.append("TimeoutReached");
161 uma_size_label.append("TimeoutReached");
162 break;
163 case StopReason::METADATA_ERROR:
164 NOTREACHED();
165 break;
166 }
167 } else if (status) {
168 switch (status.value()) {
169 case Status::DONE_REACHED_MAX:
170 uma_count_label.append("MaxIterations");
171 uma_size_label.append("MaxIterations");
172 break;
173 case Status::DONE_ERROR:
174 base::UmaHistogramEnumeration(
175 "WebCore.IndexedDB.TombstoneSweeper.SweepError",
176 leveldb_env::GetLevelDBStatusUMAValue(leveldb_error),
177 leveldb_env::LEVELDB_STATUS_MAX);
178 uma_count_label.append("SweepError");
179 uma_size_label.append("SweepError");
180 break;
181 case Status::DONE_COMPLETE:
182 uma_count_label.append("Complete");
183 uma_size_label.append("Complete");
184 break;
185 case Status::SWEEPING:
186 NOTREACHED();
187 break;
188 }
189 } else {
190 NOTREACHED();
191 }
192
193 // Some stats are only recorded for completed runs.
194 if (status && status.value() == Status::DONE_COMPLETE) {
195 if (start_time_) {
196 base::TimeDelta total_time =
197 (clock_for_testing_ ? clock_for_testing_->NowTicks()
198 : base::TimeTicks::Now()) -
199 start_time_.value();
200
201 base::UmaHistogramTimes(
202 "WebCore.IndexedDB.TombstoneSweeper.DeletionTotalTime.Complete",
203 total_time);
204 if (metrics_.seen_tombstones > 0) {
205 // Only record deletion time if we do a deletion.
206 base::UmaHistogramTimes(
207 "WebCore.IndexedDB.TombstoneSweeper.DeletionCommitTime."
208 "Complete",
209 total_deletion_time_);
210 }
211 }
212 }
213
214 base::HistogramBase* count_histogram = base::Histogram::FactoryGet(
215 uma_count_label, 1, 1'000'000, 50,
216 base::HistogramBase::kUmaTargetedHistogramFlag);
217 // Range of 1 byte to 100 MB.
218 base::HistogramBase* size_histogram = base::Histogram::FactoryGet(
219 uma_size_label, 1, 100'000'000, 50,
220 base::HistogramBase::kUmaTargetedHistogramFlag);
221
222 if (count_histogram)
223 count_histogram->Add(metrics_.seen_tombstones);
224 if (size_histogram)
225 size_histogram->Add(metrics_.seen_tombstones_size);
226
227 // We put our max at 20 instead of 100 to reduce the number of buckets.
228 if (total_indices_ > 0) {
229 static const int kIndexPercentageBucketCount = 20;
230 base::UmaHistogramExactLinear(
231 "WebCore.IndexedDB.TombstoneSweeper.IndexScanPercent",
232 indices_scanned_ * kIndexPercentageBucketCount / total_indices_,
233 kIndexPercentageBucketCount + 1);
234 }
235 }
236
FlushDeletions()237 leveldb::Status IndexedDBTombstoneSweeper::FlushDeletions() {
238 if (!has_writes_)
239 return leveldb::Status::OK();
240 base::TimeTicks start = base::TimeTicks::Now();
241
242 leveldb::Status status =
243 database()->Write(leveldb::WriteOptions(), &round_deletion_batch_);
244 round_deletion_batch_.Clear();
245 has_writes_ = false;
246
247 if (!status.ok()) {
248 base::UmaHistogramEnumeration(
249 "WebCore.IndexedDB.TombstoneSweeper.DeletionWriteError",
250 leveldb_env::GetLevelDBStatusUMAValue(status),
251 leveldb_env::LEVELDB_STATUS_MAX);
252 return status;
253 }
254
255 base::TimeDelta diff = base::TimeTicks::Now() - start;
256 total_deletion_time_ += diff;
257 return status;
258 }
259
ShouldContinueIteration(IndexedDBTombstoneSweeper::Status * sweep_status,leveldb::Status * leveldb_status,int * round_iterations)260 bool IndexedDBTombstoneSweeper::ShouldContinueIteration(
261 IndexedDBTombstoneSweeper::Status* sweep_status,
262 leveldb::Status* leveldb_status,
263 int* round_iterations) {
264 ++num_iterations_;
265 ++(*round_iterations);
266
267 if (!iterator_->Valid()) {
268 *leveldb_status = iterator_->status();
269 if (!leveldb_status->ok()) {
270 *sweep_status = Status::DONE_ERROR;
271 return false;
272 }
273 *sweep_status = Status::SWEEPING;
274 return true;
275 }
276 if (*round_iterations >= max_round_iterations_) {
277 *sweep_status = Status::SWEEPING;
278 return false;
279 }
280 if (num_iterations_ >= max_iterations_) {
281 *sweep_status = Status::DONE_REACHED_MAX;
282 return false;
283 }
284 return true;
285 }
286
DoSweep(leveldb::Status * leveldb_status)287 IndexedDBTombstoneSweeper::Status IndexedDBTombstoneSweeper::DoSweep(
288 leveldb::Status* leveldb_status) {
289 int round_iterations = 0;
290 Status sweep_status;
291 if (database_metadata_->empty())
292 return Status::DONE_COMPLETE;
293
294 if (!iterator_) {
295 leveldb::ReadOptions iterator_options;
296 iterator_options.fill_cache = false;
297 iterator_options.verify_checksums = true;
298 iterator_.reset(database()->NewIterator(iterator_options));
299 }
300
301 if (!sweep_state_.database_it) {
302 size_t start_database_idx = static_cast<size_t>(
303 sweep_state_.start_database_seed % database_metadata_->size());
304 sweep_state_.database_it = WrappingIterator<DatabaseMetadataVector>(
305 database_metadata_, start_database_idx);
306 }
307 // Loop conditions facilitate starting at random index.
308 for (; sweep_state_.database_it.value().IsValid();
309 sweep_state_.database_it.value().Next()) {
310 const IndexedDBDatabaseMetadata& database =
311 sweep_state_.database_it.value().Value();
312 if (database.object_stores.empty())
313 continue;
314
315 if (!sweep_state_.object_store_it) {
316 size_t start_object_store_idx = static_cast<size_t>(
317 sweep_state_.start_object_store_seed % database.object_stores.size());
318 sweep_state_.object_store_it = WrappingIterator<ObjectStoreMetadataMap>(
319 &database.object_stores, start_object_store_idx);
320 }
321 // Loop conditions facilitate starting at random index.
322 for (; sweep_state_.object_store_it.value().IsValid();
323 sweep_state_.object_store_it.value().Next()) {
324 const IndexedDBObjectStoreMetadata& object_store =
325 sweep_state_.object_store_it.value().Value().second;
326
327 if (object_store.indexes.empty())
328 continue;
329
330 if (!sweep_state_.index_it) {
331 size_t start_index_idx = static_cast<size_t>(
332 sweep_state_.start_index_seed % object_store.indexes.size());
333 sweep_state_.index_it = WrappingIterator<IndexMetadataMap>(
334 &object_store.indexes, start_index_idx);
335 }
336 // Loop conditions facilitate starting at random index.
337 for (; sweep_state_.index_it.value().IsValid();
338 sweep_state_.index_it.value().Next()) {
339 const IndexedDBIndexMetadata& index =
340 sweep_state_.index_it.value().Value().second;
341
342 bool can_continue =
343 IterateIndex(database.id, object_store.id, index, &sweep_status,
344 leveldb_status, &round_iterations);
345 if (!can_continue)
346 return sweep_status;
347 }
348 sweep_state_.index_it = base::nullopt;
349 }
350 sweep_state_.object_store_it = base::nullopt;
351 }
352 return Status::DONE_COMPLETE;
353 }
354
IterateIndex(int64_t database_id,int64_t object_store_id,const IndexedDBIndexMetadata & index,IndexedDBTombstoneSweeper::Status * sweep_status,leveldb::Status * leveldb_status,int * round_iterations)355 bool IndexedDBTombstoneSweeper::IterateIndex(
356 int64_t database_id,
357 int64_t object_store_id,
358 const IndexedDBIndexMetadata& index,
359 IndexedDBTombstoneSweeper::Status* sweep_status,
360 leveldb::Status* leveldb_status,
361 int* round_iterations) {
362 // If the sweeper exited early from an index scan, continue where it left off.
363 if (sweep_state_.index_it_key) {
364 iterator_->Seek(sweep_state_.index_it_key.value().Encode());
365 if (!ShouldContinueIteration(sweep_status, leveldb_status,
366 round_iterations)) {
367 return false;
368 }
369 // Start at the first unvisited value.
370 iterator_->Next();
371 if (!ShouldContinueIteration(sweep_status, leveldb_status,
372 round_iterations)) {
373 return false;
374 }
375 } else {
376 iterator_->Seek(
377 IndexDataKey::EncodeMinKey(database_id, object_store_id, index.id));
378 if (!ShouldContinueIteration(sweep_status, leveldb_status,
379 round_iterations)) {
380 return false;
381 }
382 }
383
384 while (iterator_->Valid()) {
385 leveldb::Slice key_slice = iterator_->key();
386 base::StringPiece index_key_str = leveldb_env::MakeStringPiece(key_slice);
387 size_t key_size = index_key_str.size();
388 base::StringPiece index_value_str =
389 leveldb_env::MakeStringPiece(iterator_->value());
390 size_t value_size = index_value_str.size();
391 // See if we've reached the end of the current index or all indexes.
392 sweep_state_.index_it_key.emplace(IndexDataKey());
393 if (!IndexDataKey::Decode(&index_key_str,
394 &sweep_state_.index_it_key.value()) ||
395 sweep_state_.index_it_key.value().IndexId() != index.id) {
396 break;
397 }
398
399 size_t entry_size = key_size + value_size;
400
401 int64_t index_data_version;
402 std::unique_ptr<IndexedDBKey> primary_key;
403
404 if (!DecodeVarInt(&index_value_str, &index_data_version)) {
405 ++metrics_.num_invalid_index_values;
406 iterator_->Next();
407 if (!ShouldContinueIteration(sweep_status, leveldb_status,
408 round_iterations)) {
409 return false;
410 }
411 continue;
412 }
413 std::string encoded_primary_key = index_value_str.as_string();
414 std::string exists_key = ExistsEntryKey::Encode(
415 database_id, object_store_id, encoded_primary_key);
416
417 std::string exists_value;
418 leveldb::Status s =
419 database()->Get(leveldb::ReadOptions(), exists_key, &exists_value);
420 if (!s.ok()) {
421 ++metrics_.num_errors_reading_exists_table;
422 iterator_->Next();
423 if (!ShouldContinueIteration(sweep_status, leveldb_status,
424 round_iterations)) {
425 return false;
426 }
427 continue;
428 }
429 base::StringPiece exists_value_piece(exists_value);
430 int64_t decoded_exists_version;
431 if (!DecodeInt(&exists_value_piece, &decoded_exists_version) ||
432 !exists_value_piece.empty()) {
433 ++metrics_.num_invalid_exists_values;
434 iterator_->Next();
435 if (!ShouldContinueIteration(sweep_status, leveldb_status,
436 round_iterations)) {
437 return false;
438 }
439 continue;
440 }
441
442 if (decoded_exists_version != index_data_version) {
443 has_writes_ = true;
444 round_deletion_batch_.Delete(key_slice);
445 ++metrics_.seen_tombstones;
446 metrics_.seen_tombstones_size += entry_size;
447 }
448
449 iterator_->Next();
450 if (!ShouldContinueIteration(sweep_status, leveldb_status,
451 round_iterations)) {
452 return false;
453 }
454 }
455 ++indices_scanned_;
456 sweep_state_.index_it_key = base::nullopt;
457 return true;
458 }
459
460 } // namespace content
461