1 // Copyright 2016 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 "components/services/storage/dom_storage/storage_area_impl.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/containers/span.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/threading/thread_task_runner_handle.h"
12 #include "base/trace_event/memory_dump_manager.h"
13 #include "base/trace_event/process_memory_dump.h"
14 #include "components/services/storage/dom_storage/async_dom_storage_database.h"
15 #include "third_party/leveldatabase/env_chromium.h"
16
17 namespace storage {
18
19 StorageAreaImpl::Delegate::~Delegate() = default;
20
PrepareToCommit(std::vector<DomStorageDatabase::KeyValuePair> * extra_entries_to_add,std::vector<DomStorageDatabase::Key> * extra_keys_to_delete)21 void StorageAreaImpl::Delegate::PrepareToCommit(
22 std::vector<DomStorageDatabase::KeyValuePair>* extra_entries_to_add,
23 std::vector<DomStorageDatabase::Key>* extra_keys_to_delete) {}
24
MigrateData(base::OnceCallback<void (std::unique_ptr<ValueMap>)> callback)25 void StorageAreaImpl::Delegate::MigrateData(
26 base::OnceCallback<void(std::unique_ptr<ValueMap>)> callback) {
27 std::move(callback).Run(nullptr);
28 }
29
FixUpData(const ValueMap & data)30 std::vector<StorageAreaImpl::Change> StorageAreaImpl::Delegate::FixUpData(
31 const ValueMap& data) {
32 return std::vector<Change>();
33 }
34
OnMapLoaded(leveldb::Status)35 void StorageAreaImpl::Delegate::OnMapLoaded(leveldb::Status) {}
36
37 bool StorageAreaImpl::s_aggressive_flushing_enabled_ = false;
38
RateLimiter(size_t desired_rate,base::TimeDelta time_quantum)39 StorageAreaImpl::RateLimiter::RateLimiter(size_t desired_rate,
40 base::TimeDelta time_quantum)
41 : rate_(desired_rate), samples_(0), time_quantum_(time_quantum) {
42 DCHECK_GT(desired_rate, 0ul);
43 }
44
ComputeTimeNeeded() const45 base::TimeDelta StorageAreaImpl::RateLimiter::ComputeTimeNeeded() const {
46 return time_quantum_ * (samples_ / rate_);
47 }
48
ComputeDelayNeeded(const base::TimeDelta elapsed_time) const49 base::TimeDelta StorageAreaImpl::RateLimiter::ComputeDelayNeeded(
50 const base::TimeDelta elapsed_time) const {
51 base::TimeDelta time_needed = ComputeTimeNeeded();
52 if (time_needed > elapsed_time)
53 return time_needed - elapsed_time;
54 return base::TimeDelta();
55 }
56
57 StorageAreaImpl::CommitBatch::CommitBatch() = default;
58
59 StorageAreaImpl::CommitBatch::~CommitBatch() = default;
60
StorageAreaImpl(AsyncDomStorageDatabase * database,const std::string & prefix,Delegate * delegate,const Options & options)61 StorageAreaImpl::StorageAreaImpl(AsyncDomStorageDatabase* database,
62 const std::string& prefix,
63 Delegate* delegate,
64 const Options& options)
65 : StorageAreaImpl(database,
66 std::vector<uint8_t>(prefix.begin(), prefix.end()),
67 delegate,
68 options) {}
69
StorageAreaImpl(AsyncDomStorageDatabase * database,std::vector<uint8_t> prefix,Delegate * delegate,const Options & options)70 StorageAreaImpl::StorageAreaImpl(AsyncDomStorageDatabase* database,
71 std::vector<uint8_t> prefix,
72 Delegate* delegate,
73 const Options& options)
74 : prefix_(std::move(prefix)),
75 delegate_(delegate),
76 database_(database),
77 cache_mode_(database ? options.cache_mode : CacheMode::KEYS_AND_VALUES),
78 storage_used_(0),
79 max_size_(options.max_size),
80 memory_used_(0),
81 start_time_(base::TimeTicks::Now()),
82 default_commit_delay_(options.default_commit_delay),
83 data_rate_limiter_(options.max_bytes_per_hour,
84 base::TimeDelta::FromHours(1)),
85 commit_rate_limiter_(options.max_commits_per_hour,
86 base::TimeDelta::FromHours(1)) {
87 receivers_.set_disconnect_handler(base::BindRepeating(
88 &StorageAreaImpl::OnConnectionError, weak_ptr_factory_.GetWeakPtr()));
89 }
90
~StorageAreaImpl()91 StorageAreaImpl::~StorageAreaImpl() {
92 DCHECK(!has_pending_load_tasks());
93 if (commit_batch_)
94 CommitChanges();
95 }
96
InitializeAsEmpty()97 void StorageAreaImpl::InitializeAsEmpty() {
98 DCHECK_EQ(map_state_, MapState::UNLOADED);
99 map_state_ = MapState::LOADING_FROM_DATABASE;
100 OnMapLoaded(leveldb::Status::OK(), {});
101 }
102
Bind(mojo::PendingReceiver<blink::mojom::StorageArea> receiver)103 void StorageAreaImpl::Bind(
104 mojo::PendingReceiver<blink::mojom::StorageArea> receiver) {
105 receivers_.Add(this, std::move(receiver));
106 // If the number of bindings is more than 1, then the |client_old_value| sent
107 // by the clients need not be valid due to races on updates from multiple
108 // clients. So, cache the values in the service. Setting cache mode back to
109 // only keys when the number of bindings goes back to 1 could cause
110 // inconsistency due to the async notifications of mutations to the client
111 // reaching late.
112 if (cache_mode_ == CacheMode::KEYS_ONLY_WHEN_POSSIBLE &&
113 receivers_.size() > 1) {
114 SetCacheMode(CacheMode::KEYS_AND_VALUES);
115 }
116 }
117
ForkToNewPrefix(const std::string & new_prefix,Delegate * delegate,const Options & options)118 std::unique_ptr<StorageAreaImpl> StorageAreaImpl::ForkToNewPrefix(
119 const std::string& new_prefix,
120 Delegate* delegate,
121 const Options& options) {
122 return ForkToNewPrefix(
123 std::vector<uint8_t>(new_prefix.begin(), new_prefix.end()), delegate,
124 options);
125 }
126
ForkToNewPrefix(std::vector<uint8_t> new_prefix,Delegate * delegate,const Options & options)127 std::unique_ptr<StorageAreaImpl> StorageAreaImpl::ForkToNewPrefix(
128 std::vector<uint8_t> new_prefix,
129 Delegate* delegate,
130 const Options& options) {
131 auto forked_area = std::make_unique<StorageAreaImpl>(
132 database_, std::move(new_prefix), delegate, options);
133 // If the source map is empty, don't bother hitting disk.
134 if (IsMapLoadedAndEmpty()) {
135 forked_area->InitializeAsEmpty();
136 return forked_area;
137 }
138 forked_area->map_state_ = MapState::LOADING_FROM_FORK;
139
140 if (IsMapLoaded()) {
141 DoForkOperation(forked_area->weak_ptr_factory_.GetWeakPtr());
142 } else {
143 LoadMap(base::BindOnce(&StorageAreaImpl::DoForkOperation,
144 weak_ptr_factory_.GetWeakPtr(),
145 forked_area->weak_ptr_factory_.GetWeakPtr()));
146 }
147 return forked_area;
148 }
149
CancelAllPendingRequests()150 void StorageAreaImpl::CancelAllPendingRequests() {
151 on_load_complete_tasks_.clear();
152 }
153
EnableAggressiveCommitDelay()154 void StorageAreaImpl::EnableAggressiveCommitDelay() {
155 s_aggressive_flushing_enabled_ = true;
156 }
157
ScheduleImmediateCommit(base::OnceClosure callback)158 void StorageAreaImpl::ScheduleImmediateCommit(base::OnceClosure callback) {
159 if (!on_load_complete_tasks_.empty()) {
160 LoadMap(base::BindOnce(&StorageAreaImpl::ScheduleImmediateCommit,
161 weak_ptr_factory_.GetWeakPtr(),
162 std::move(callback)));
163 return;
164 }
165
166 if (!database_ || !commit_batch_) {
167 if (callback)
168 std::move(callback).Run();
169 return;
170 }
171 CommitChanges(std::move(callback));
172 }
173
OnMemoryDump(const std::string & name,base::trace_event::ProcessMemoryDump * pmd)174 void StorageAreaImpl::OnMemoryDump(const std::string& name,
175 base::trace_event::ProcessMemoryDump* pmd) {
176 if (!IsMapLoaded())
177 return;
178
179 const char* system_allocator_name =
180 base::trace_event::MemoryDumpManager::GetInstance()
181 ->system_allocator_pool_name();
182 if (commit_batch_) {
183 size_t data_size = 0;
184 for (const auto& iter : commit_batch_->changed_values)
185 data_size += iter.first.size() + iter.second.size();
186 for (const auto& key : commit_batch_->changed_keys)
187 data_size += key.size();
188
189 auto* commit_batch_mad = pmd->CreateAllocatorDump(name + "/commit_batch");
190 commit_batch_mad->AddScalar(
191 base::trace_event::MemoryAllocatorDump::kNameSize,
192 base::trace_event::MemoryAllocatorDump::kUnitsBytes, data_size);
193 if (system_allocator_name)
194 pmd->AddSuballocation(commit_batch_mad->guid(), system_allocator_name);
195 }
196
197 // Do not add storage map usage if less than 1KB.
198 if (memory_used_ < 1024)
199 return;
200
201 auto* map_mad = pmd->CreateAllocatorDump(name + "/storage_map");
202 map_mad->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
203 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
204 memory_used_);
205 map_mad->AddString("load_state", "",
206 map_state_ == MapState::LOADED_KEYS_ONLY
207 ? "keys_only"
208 : "keys_and_values");
209 if (system_allocator_name)
210 pmd->AddSuballocation(map_mad->guid(), system_allocator_name);
211 }
212
PurgeMemory()213 void StorageAreaImpl::PurgeMemory() {
214 if (!IsMapLoaded() || // We're not using any memory.
215 commit_batch_ || // We leave things alone with changes pending.
216 !database_) { // Don't purge anything if we're not backed by a database.
217 return;
218 }
219
220 map_state_ = MapState::UNLOADED;
221 memory_used_ = 0;
222 keys_only_map_.clear();
223 keys_values_map_.clear();
224 }
225
SetCacheModeForTesting(CacheMode cache_mode)226 void StorageAreaImpl::SetCacheModeForTesting(CacheMode cache_mode) {
227 SetCacheMode(cache_mode);
228 }
229
AddObserver(mojo::PendingRemote<blink::mojom::StorageAreaObserver> observer)230 void StorageAreaImpl::AddObserver(
231 mojo::PendingRemote<blink::mojom::StorageAreaObserver> observer) {
232 mojo::Remote<blink::mojom::StorageAreaObserver> observer_remote(
233 std::move(observer));
234 if (cache_mode_ == CacheMode::KEYS_AND_VALUES)
235 observer_remote->ShouldSendOldValueOnMutations(false);
236 observers_.Add(std::move(observer_remote));
237 }
238
Put(const std::vector<uint8_t> & key,const std::vector<uint8_t> & value,const base::Optional<std::vector<uint8_t>> & client_old_value,const std::string & source,PutCallback callback)239 void StorageAreaImpl::Put(
240 const std::vector<uint8_t>& key,
241 const std::vector<uint8_t>& value,
242 const base::Optional<std::vector<uint8_t>>& client_old_value,
243 const std::string& source,
244 PutCallback callback) {
245 if (!IsMapLoaded() || IsMapUpgradeNeeded()) {
246 LoadMap(base::BindOnce(&StorageAreaImpl::Put,
247 weak_ptr_factory_.GetWeakPtr(), key, value,
248 client_old_value, source, std::move(callback)));
249 return;
250 }
251
252 size_t old_item_size = 0;
253 size_t old_item_memory = 0;
254 size_t new_item_memory = 0;
255 base::Optional<std::vector<uint8_t>> old_value;
256 if (map_state_ == MapState::LOADED_KEYS_ONLY) {
257 KeysOnlyMap::const_iterator found = keys_only_map_.find(key);
258 if (found != keys_only_map_.end()) {
259 if (client_old_value &&
260 client_old_value.value().size() == found->second) {
261 if (client_old_value == value) {
262 // NOTE: Even though the key is not changing, we have to acknowledge
263 // the change request, as clients may rely on this acknowledgement for
264 // caching behavior.
265 for (const auto& observer : observers_)
266 observer->KeyChanged(key, value, value, source);
267 std::move(callback).Run(true); // Key already has this value.
268 return;
269 }
270 old_value = client_old_value.value();
271 } else {
272 #if DCHECK_IS_ON()
273 // If |client_old_value| was not provided or if it's size does not
274 // match, then we still let the change go through. But the notification
275 // sent to clients will not contain old value. This is okay since
276 // currently the only observer to these notification is the client
277 // itself.
278 DVLOG(1) << "Storage area with prefix "
279 << std::string(prefix_.begin(), prefix_.end())
280 << ": past value has length of " << found->second << ", but:";
281 if (client_old_value) {
282 DVLOG(1) << "Given past value has incorrect length of "
283 << client_old_value.value().size();
284 } else {
285 DVLOG(1) << "No given past value was provided.";
286 }
287 #endif
288 }
289 old_item_size = key.size() + found->second;
290 old_item_memory = key.size() + sizeof(size_t);
291 }
292 new_item_memory = key.size() + sizeof(size_t);
293 } else {
294 DCHECK_EQ(map_state_, MapState::LOADED_KEYS_AND_VALUES);
295 auto found = keys_values_map_.find(key);
296 if (found != keys_values_map_.end()) {
297 if (found->second == value) {
298 // NOTE: Even though the key is not changing, we have to acknowledge
299 // the change request, as clients may rely on this acknowledgement for
300 // caching behavior.
301 for (const auto& observer : observers_)
302 observer->KeyChanged(key, value, value, source);
303 std::move(callback).Run(true); // Key already has this value.
304 return;
305 }
306 old_value = std::move(found->second);
307 old_item_size = key.size() + old_value.value().size();
308 old_item_memory = old_item_size;
309 }
310 new_item_memory = key.size() + value.size();
311 }
312
313 size_t new_item_size = key.size() + value.size();
314 size_t new_storage_used = storage_used_ - old_item_size + new_item_size;
315
316 // Only check quota if the size is increasing, this allows
317 // shrinking changes to pre-existing maps that are over budget.
318 if (new_item_size > old_item_size && new_storage_used > max_size_) {
319 if (map_state_ == MapState::LOADED_KEYS_ONLY) {
320 receivers_.ReportBadMessage(
321 "The quota in browser cannot exceed when there is only one "
322 "renderer.");
323 } else {
324 for (const auto& observer : observers_)
325 observer->KeyChangeFailed(key, source);
326 std::move(callback).Run(false);
327 }
328 return;
329 }
330
331 if (database_) {
332 CreateCommitBatchIfNeeded();
333 // No need to store values in |commit_batch_| if values are already
334 // available in |keys_values_map_|, since CommitChanges() will take values
335 // from there.
336 if (map_state_ == MapState::LOADED_KEYS_ONLY)
337 commit_batch_->changed_values[key] = value;
338 else
339 commit_batch_->changed_keys.insert(key);
340 }
341
342 if (map_state_ == MapState::LOADED_KEYS_ONLY)
343 keys_only_map_[key] = value.size();
344 else
345 keys_values_map_[key] = value;
346
347 storage_used_ = new_storage_used;
348 memory_used_ += new_item_memory - old_item_memory;
349 for (const auto& observer : observers_)
350 observer->KeyChanged(key, value, old_value, source);
351 std::move(callback).Run(true);
352 }
353
Delete(const std::vector<uint8_t> & key,const base::Optional<std::vector<uint8_t>> & client_old_value,const std::string & source,DeleteCallback callback)354 void StorageAreaImpl::Delete(
355 const std::vector<uint8_t>& key,
356 const base::Optional<std::vector<uint8_t>>& client_old_value,
357 const std::string& source,
358 DeleteCallback callback) {
359 // Map upgrade check is required because the cache state could be changed
360 // due to multiple bindings, and when multiple bindings are involved the
361 // |client_old_value| can race. Thus any changes require checking for an
362 // upgrade.
363 if (!IsMapLoaded() || IsMapUpgradeNeeded()) {
364 LoadMap(base::BindOnce(&StorageAreaImpl::Delete,
365 weak_ptr_factory_.GetWeakPtr(), key,
366 client_old_value, source, std::move(callback)));
367 return;
368 }
369
370 if (database_)
371 CreateCommitBatchIfNeeded();
372
373 std::vector<uint8_t> old_value;
374 if (map_state_ == MapState::LOADED_KEYS_ONLY) {
375 KeysOnlyMap::const_iterator found = keys_only_map_.find(key);
376 if (found == keys_only_map_.end()) {
377 // NOTE: Even though the key is not changing, we have to acknowledge
378 // the change request, as clients may rely on this acknowledgement for
379 // caching behavior.
380 for (const auto& observer : observers_)
381 observer->KeyDeleted(key, base::nullopt, source);
382 std::move(callback).Run(true);
383 return;
384 }
385 if (client_old_value && client_old_value.value().size() == found->second) {
386 old_value = client_old_value.value();
387 } else {
388 #if DCHECK_IS_ON()
389 // If |client_old_value| was not provided or if it's size does not match,
390 // then we still let the change go through. But the notification sent to
391 // clients will not contain old value. This is okay since currently the
392 // only observer to these notification is the client itself.
393 DVLOG(1) << "Storage area with prefix "
394 << std::string(prefix_.begin(), prefix_.end())
395 << ": past value has length of " << found->second << ", but:";
396 if (client_old_value) {
397 DVLOG(1) << "Given past value has incorrect length of "
398 << client_old_value.value().size();
399 } else {
400 DVLOG(1) << "No given past value was provided.";
401 }
402 #endif
403 }
404 storage_used_ -= key.size() + found->second;
405 keys_only_map_.erase(found);
406 memory_used_ -= key.size() + sizeof(size_t);
407 if (commit_batch_)
408 commit_batch_->changed_values[key] = std::vector<uint8_t>();
409 } else {
410 DCHECK_EQ(map_state_, MapState::LOADED_KEYS_AND_VALUES);
411 auto found = keys_values_map_.find(key);
412 if (found == keys_values_map_.end()) {
413 // NOTE: Even though the key is not changing, we have to acknowledge
414 // the change request, as clients may rely on this acknowledgement for
415 // caching behavior.
416 for (const auto& observer : observers_)
417 observer->KeyDeleted(key, base::nullopt, source);
418 std::move(callback).Run(true);
419 return;
420 }
421 old_value.swap(found->second);
422 keys_values_map_.erase(found);
423 memory_used_ -= key.size() + old_value.size();
424 storage_used_ -= key.size() + old_value.size();
425 if (commit_batch_)
426 commit_batch_->changed_keys.insert(key);
427 }
428
429 for (auto& observer : observers_)
430 observer->KeyDeleted(key, old_value, source);
431 std::move(callback).Run(true);
432 }
433
DeleteAll(const std::string & source,mojo::PendingRemote<blink::mojom::StorageAreaObserver> new_observer,DeleteAllCallback callback)434 void StorageAreaImpl::DeleteAll(
435 const std::string& source,
436 mojo::PendingRemote<blink::mojom::StorageAreaObserver> new_observer,
437 DeleteAllCallback callback) {
438 // Don't check if a map upgrade is needed here and instead just create an
439 // empty map ourself.
440 if (!IsMapLoaded()) {
441 LoadMap(base::BindOnce(&StorageAreaImpl::DeleteAll,
442 weak_ptr_factory_.GetWeakPtr(), source,
443 std::move(new_observer), std::move(callback)));
444 return;
445 }
446
447 bool already_empty = IsMapLoadedAndEmpty();
448
449 // Upgrade map state if needed.
450 if (IsMapUpgradeNeeded()) {
451 DCHECK(keys_values_map_.empty());
452 map_state_ = MapState::LOADED_KEYS_AND_VALUES;
453 }
454
455 if (new_observer)
456 AddObserver(std::move(new_observer));
457
458 if (already_empty) {
459 for (const auto& observer : observers_)
460 observer->AllDeleted(/*was_nonempty=*/false, source);
461 std::move(callback).Run(true);
462 return;
463 }
464
465 if (database_) {
466 CreateCommitBatchIfNeeded();
467 commit_batch_->clear_all_first = true;
468 commit_batch_->changed_values.clear();
469 commit_batch_->changed_keys.clear();
470 }
471
472 keys_only_map_.clear();
473 keys_values_map_.clear();
474
475 storage_used_ = 0;
476 memory_used_ = 0;
477 for (const auto& observer : observers_)
478 observer->AllDeleted(/*was_nonempty=*/true, source);
479 std::move(callback).Run(/*success=*/true);
480 }
481
Get(const std::vector<uint8_t> & key,GetCallback callback)482 void StorageAreaImpl::Get(const std::vector<uint8_t>& key,
483 GetCallback callback) {
484 // TODO(ssid): Remove this method since it is not supported in only keys mode,
485 // crbug.com/764127.
486 if (cache_mode_ == CacheMode::KEYS_ONLY_WHEN_POSSIBLE) {
487 NOTREACHED();
488 return;
489 }
490 if (!IsMapLoaded() || IsMapUpgradeNeeded()) {
491 LoadMap(base::BindOnce(&StorageAreaImpl::Get,
492 weak_ptr_factory_.GetWeakPtr(), key,
493 std::move(callback)));
494 return;
495 }
496
497 auto found = keys_values_map_.find(key);
498 if (found == keys_values_map_.end()) {
499 std::move(callback).Run(false, std::vector<uint8_t>());
500 return;
501 }
502 std::move(callback).Run(true, found->second);
503 }
504
GetAll(mojo::PendingRemote<blink::mojom::StorageAreaObserver> new_observer,GetAllCallback callback)505 void StorageAreaImpl::GetAll(
506 mojo::PendingRemote<blink::mojom::StorageAreaObserver> new_observer,
507 GetAllCallback callback) {
508 // If the map is keys-only and empty, then no loading is necessary.
509 if (IsMapLoadedAndEmpty()) {
510 std::move(callback).Run(std::vector<blink::mojom::KeyValuePtr>());
511 if (new_observer)
512 AddObserver(std::move(new_observer));
513 return;
514 }
515
516 // The map must always be loaded for the KEYS_ONLY_WHEN_POSSIBLE mode.
517 if (map_state_ != MapState::LOADED_KEYS_AND_VALUES) {
518 LoadMap(base::BindOnce(&StorageAreaImpl::GetAll,
519 weak_ptr_factory_.GetWeakPtr(),
520 std::move(new_observer), std::move(callback)));
521 return;
522 }
523
524 std::vector<blink::mojom::KeyValuePtr> all;
525 for (const auto& it : keys_values_map_) {
526 auto kv = blink::mojom::KeyValue::New();
527 kv->key = it.first;
528 kv->value = it.second;
529 all.push_back(std::move(kv));
530 }
531 std::move(callback).Run(std::move(all));
532 if (new_observer)
533 AddObserver(std::move(new_observer));
534 }
535
SetCacheMode(CacheMode cache_mode)536 void StorageAreaImpl::SetCacheMode(CacheMode cache_mode) {
537 if (cache_mode_ == cache_mode ||
538 (!database_ && cache_mode == CacheMode::KEYS_ONLY_WHEN_POSSIBLE)) {
539 return;
540 }
541 cache_mode_ = cache_mode;
542 bool should_send_values = cache_mode == CacheMode::KEYS_ONLY_WHEN_POSSIBLE;
543 for (auto& observer : observers_)
544 observer->ShouldSendOldValueOnMutations(should_send_values);
545
546 // If the |keys_only_map_| is loaded and desired state needs values, no point
547 // keeping around the map since the next change would require reload. On the
548 // other hand if only keys are desired, the keys and values map can still be
549 // used. Consider not unloading when the map is still useful.
550 UnloadMapIfPossible();
551 }
552
OnConnectionError()553 void StorageAreaImpl::OnConnectionError() {
554 if (!receivers_.empty())
555 return;
556 // If any tasks are waiting for load to complete, delay calling the
557 // no_bindings_callback_ until all those tasks have completed.
558 if (!on_load_complete_tasks_.empty())
559 return;
560 delegate_->OnNoBindings();
561 }
562
LoadMap(base::OnceClosure completion_callback)563 void StorageAreaImpl::LoadMap(base::OnceClosure completion_callback) {
564 DCHECK_NE(map_state_, MapState::LOADED_KEYS_AND_VALUES);
565 DCHECK(keys_values_map_.empty());
566
567 // Current commit batch needs to be applied before re-loading the map. The
568 // re-load of map occurs only when GetAll() is called or CacheMode is set to
569 // keys and values, and the |keys_only_map_| is already loaded. In this case
570 // commit batch needs to be committed before reading the database.
571 if (map_state_ == MapState::LOADED_KEYS_ONLY) {
572 DCHECK(on_load_complete_tasks_.empty());
573 DCHECK(database_);
574 if (commit_batch_)
575 CommitChanges();
576 // Make sure the keys only map is not used when on load tasks are in queue.
577 // The changes to the area will be queued to on load tasks.
578 keys_only_map_.clear();
579 map_state_ = MapState::UNLOADED;
580 }
581
582 on_load_complete_tasks_.push_back(std::move(completion_callback));
583 if (map_state_ == MapState::LOADING_FROM_DATABASE ||
584 map_state_ == MapState::LOADING_FROM_FORK) {
585 return;
586 }
587
588 map_state_ = MapState::LOADING_FROM_DATABASE;
589
590 if (!database_) {
591 OnMapLoaded(leveldb::Status::IOError(""), {});
592 return;
593 }
594
595 database_->RunDatabaseTask(
596 base::BindOnce(
597 [](const DomStorageDatabase::Key& prefix,
598 const DomStorageDatabase& db) {
599 std::vector<DomStorageDatabase::KeyValuePair> data;
600 leveldb::Status status = db.GetPrefixed(prefix, &data);
601 return std::make_tuple(status, std::move(data));
602 },
603 prefix_),
604 base::BindOnce(&StorageAreaImpl::OnMapLoaded,
605 weak_ptr_factory_.GetWeakPtr()));
606 }
607
OnMapLoaded(leveldb::Status status,std::vector<DomStorageDatabase::KeyValuePair> data)608 void StorageAreaImpl::OnMapLoaded(
609 leveldb::Status status,
610 std::vector<DomStorageDatabase::KeyValuePair> data) {
611 DCHECK(keys_values_map_.empty());
612 DCHECK_EQ(map_state_, MapState::LOADING_FROM_DATABASE);
613
614 if (data.empty() && status.ok()) {
615 delegate_->MigrateData(base::BindOnce(&StorageAreaImpl::OnGotMigrationData,
616 weak_ptr_factory_.GetWeakPtr()));
617 return;
618 }
619
620 keys_only_map_.clear();
621 map_state_ = MapState::LOADED_KEYS_AND_VALUES;
622
623 keys_values_map_.clear();
624 for (auto& entry : data) {
625 DCHECK_GE(entry.key.size(), prefix_.size());
626 keys_values_map_[DomStorageDatabase::Key(entry.key.begin() + prefix_.size(),
627 entry.key.end())] =
628 std::move(entry.value);
629 }
630 CalculateStorageAndMemoryUsed();
631
632 std::vector<Change> changes = delegate_->FixUpData(keys_values_map_);
633 if (!changes.empty()) {
634 DCHECK(database_);
635 CreateCommitBatchIfNeeded();
636 for (auto& change : changes) {
637 auto it = keys_values_map_.find(change.first);
638 if (!change.second) {
639 DCHECK(it != keys_values_map_.end());
640 keys_values_map_.erase(it);
641 } else {
642 if (it != keys_values_map_.end()) {
643 it->second = std::move(*change.second);
644 } else {
645 keys_values_map_[change.first] = std::move(*change.second);
646 }
647 }
648 // No need to store values in |commit_batch_| if values are already
649 // available in |keys_values_map_|, since CommitChanges() will take values
650 // from there.
651 commit_batch_->changed_keys.insert(std::move(change.first));
652 }
653 CalculateStorageAndMemoryUsed();
654 CommitChanges();
655 }
656
657 // We proceed without using a backing store, nothing will be persisted but the
658 // class is functional for the lifetime of the object.
659 delegate_->OnMapLoaded(status);
660 if (!status.ok()) {
661 database_ = nullptr;
662 SetCacheMode(CacheMode::KEYS_AND_VALUES);
663 }
664
665 if (on_load_callback_for_testing_)
666 std::move(on_load_callback_for_testing_).Run();
667
668 OnLoadComplete();
669 }
670
OnGotMigrationData(std::unique_ptr<ValueMap> data)671 void StorageAreaImpl::OnGotMigrationData(std::unique_ptr<ValueMap> data) {
672 keys_only_map_.clear();
673 keys_values_map_ = data ? std::move(*data) : ValueMap();
674 map_state_ = MapState::LOADED_KEYS_AND_VALUES;
675 CalculateStorageAndMemoryUsed();
676 delegate_->OnMapLoaded(leveldb::Status::OK());
677
678 if (database_ && !empty()) {
679 CreateCommitBatchIfNeeded();
680 // CommitChanges() will take values from |keys_values_map_|.
681 for (const auto& it : keys_values_map_)
682 commit_batch_->changed_keys.insert(it.first);
683 CommitChanges();
684 }
685 OnLoadComplete();
686 }
687
CalculateStorageAndMemoryUsed()688 void StorageAreaImpl::CalculateStorageAndMemoryUsed() {
689 memory_used_ = 0;
690 storage_used_ = 0;
691
692 for (auto& it : keys_values_map_)
693 memory_used_ += it.first.size() + it.second.size();
694 storage_used_ = memory_used_;
695
696 for (auto& it : keys_only_map_) {
697 memory_used_ += it.first.size() + sizeof(size_t);
698 storage_used_ += it.first.size() + it.second;
699 }
700 }
701
OnLoadComplete()702 void StorageAreaImpl::OnLoadComplete() {
703 DCHECK(IsMapLoaded());
704
705 std::vector<base::OnceClosure> tasks;
706 on_load_complete_tasks_.swap(tasks);
707 for (auto it = tasks.begin(); it != tasks.end(); ++it) {
708 // Some tasks (like GetAll) can require a reload if they need a different
709 // map type. If this happens, stop our task execution. Appending tasks is
710 // required (instead of replacing) because the task that required the
711 // reload-requesting-task put itself on the task queue and it still needs
712 // to be executed before the rest of the tasks.
713 if (!IsMapLoaded()) {
714 on_load_complete_tasks_.reserve(on_load_complete_tasks_.size() +
715 (tasks.end() - it));
716 std::move(it, tasks.end(), std::back_inserter(on_load_complete_tasks_));
717 return;
718 }
719 std::move(*it).Run();
720 }
721
722 // Call before |OnNoBindings| as delegate can destroy this object.
723 UnloadMapIfPossible();
724
725 // We might need to call the no_bindings_callback_ here if bindings became
726 // empty while waiting for load to complete.
727 if (receivers_.empty())
728 delegate_->OnNoBindings();
729 }
730
CreateCommitBatchIfNeeded()731 void StorageAreaImpl::CreateCommitBatchIfNeeded() {
732 if (commit_batch_)
733 return;
734 DCHECK(database_);
735
736 commit_batch_.reset(new CommitBatch());
737 StartCommitTimer();
738 }
739
StartCommitTimer()740 void StorageAreaImpl::StartCommitTimer() {
741 if (!commit_batch_)
742 return;
743
744 // Start a timer to commit any changes that accrue in the batch, but only if
745 // no commits are currently in flight. In that case the timer will be
746 // started after the commits have happened.
747 if (commit_batches_in_flight_)
748 return;
749
750 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
751 FROM_HERE,
752 base::BindOnce(&StorageAreaImpl::CommitChanges,
753 weak_ptr_factory_.GetWeakPtr(), base::OnceClosure()),
754 ComputeCommitDelay());
755 }
756
ComputeCommitDelay() const757 base::TimeDelta StorageAreaImpl::ComputeCommitDelay() const {
758 if (s_aggressive_flushing_enabled_)
759 return base::TimeDelta::FromSeconds(1);
760
761 base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time_;
762 base::TimeDelta delay =
763 std::max(default_commit_delay_,
764 std::max(commit_rate_limiter_.ComputeDelayNeeded(elapsed_time),
765 data_rate_limiter_.ComputeDelayNeeded(elapsed_time)));
766 // TODO(mek): Rename histogram to match class name, or eliminate histogram
767 // entirely.
768 UMA_HISTOGRAM_LONG_TIMES("LevelDBWrapper.CommitDelay", delay);
769 return delay;
770 }
771
CommitChanges(base::OnceClosure callback)772 void StorageAreaImpl::CommitChanges(base::OnceClosure callback) {
773 // Note: commit_batch_ may be null if ScheduleImmediateCommit was called
774 // after a delayed commit task was scheduled.
775 if (!commit_batch_) {
776 if (callback)
777 std::move(callback).Run();
778 return;
779 }
780
781 DCHECK(database_);
782 DCHECK(IsMapLoaded()) << static_cast<int>(map_state_);
783
784 commit_rate_limiter_.add_samples(1);
785
786 // Commit all our changes in a single batch.
787 struct Commit {
788 DomStorageDatabase::Key prefix;
789 bool clear_all_first;
790 std::vector<DomStorageDatabase::KeyValuePair> entries_to_add;
791 std::vector<DomStorageDatabase::Key> keys_to_delete;
792 base::Optional<DomStorageDatabase::Key> copy_to_prefix;
793 };
794
795 Commit commit;
796 commit.prefix = prefix_;
797 commit.clear_all_first = commit_batch_->clear_all_first;
798 delegate_->PrepareToCommit(&commit.entries_to_add, &commit.keys_to_delete);
799
800 const bool has_changes = !commit.entries_to_add.empty() ||
801 !commit.keys_to_delete.empty() ||
802 !commit_batch_->changed_values.empty() ||
803 !commit_batch_->changed_keys.empty();
804 size_t data_size = 0;
805 if (map_state_ == MapState::LOADED_KEYS_AND_VALUES) {
806 DCHECK(commit_batch_->changed_values.empty())
807 << "Map state and commit state out of sync.";
808 for (const auto& key : commit_batch_->changed_keys) {
809 data_size += key.size();
810 DomStorageDatabase::Key prefixed_key;
811 prefixed_key.reserve(prefix_.size() + key.size());
812 prefixed_key.insert(prefixed_key.end(), prefix_.begin(), prefix_.end());
813 prefixed_key.insert(prefixed_key.end(), key.begin(), key.end());
814 auto it = keys_values_map_.find(key);
815 if (it != keys_values_map_.end()) {
816 data_size += it->second.size();
817 commit.entries_to_add.emplace_back(std::move(prefixed_key), it->second);
818 } else {
819 commit.keys_to_delete.push_back(std::move(prefixed_key));
820 }
821 }
822 } else {
823 DCHECK(commit_batch_->changed_keys.empty())
824 << "Map state and commit state out of sync.";
825 DCHECK_EQ(map_state_, MapState::LOADED_KEYS_ONLY);
826 for (auto& entry : commit_batch_->changed_values) {
827 const auto& key = entry.first;
828 data_size += key.size();
829 DomStorageDatabase::Key prefixed_key;
830 prefixed_key.reserve(prefix_.size() + key.size());
831 prefixed_key.insert(prefixed_key.end(), prefix_.begin(), prefix_.end());
832 prefixed_key.insert(prefixed_key.end(), key.begin(), key.end());
833 auto it = keys_only_map_.find(key);
834 if (it != keys_only_map_.end()) {
835 data_size += entry.second.size();
836 commit.entries_to_add.emplace_back(std::move(prefixed_key),
837 std::move(entry.second));
838 } else {
839 commit.keys_to_delete.push_back(std::move(prefixed_key));
840 }
841 }
842 }
843 // Schedule the copy, and ignore if |clear_all_first| is specified and there
844 // are no changing keys.
845 if (commit_batch_->copy_to_prefix) {
846 DCHECK(!has_changes);
847 DCHECK(!commit_batch_->clear_all_first);
848 commit.copy_to_prefix = std::move(commit_batch_->copy_to_prefix);
849 }
850 commit_batch_.reset();
851
852 data_rate_limiter_.add_samples(data_size);
853
854 ++commit_batches_in_flight_;
855
856 database_->RunDatabaseTask(
857 base::BindOnce(
858 [](Commit commit, const DomStorageDatabase& db) {
859 leveldb::WriteBatch batch;
860 if (commit.clear_all_first)
861 db.DeletePrefixed(commit.prefix, &batch);
862 for (const auto& entry : commit.entries_to_add) {
863 batch.Put(leveldb_env::MakeSlice(entry.key),
864 leveldb_env::MakeSlice(entry.value));
865 }
866 for (const auto& key : commit.keys_to_delete)
867 batch.Delete(leveldb_env::MakeSlice(key));
868 if (commit.copy_to_prefix) {
869 db.CopyPrefixed(commit.prefix, commit.copy_to_prefix.value(),
870 &batch);
871 }
872 return db.Commit(&batch);
873 },
874 std::move(commit)),
875 base::BindOnce(&StorageAreaImpl::OnCommitComplete,
876 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
877 }
878
OnCommitComplete(base::OnceClosure callback,leveldb::Status status)879 void StorageAreaImpl::OnCommitComplete(base::OnceClosure callback,
880 leveldb::Status status) {
881 has_committed_data_ = true;
882 --commit_batches_in_flight_;
883 StartCommitTimer();
884
885 if (!status.ok())
886 SetCacheMode(CacheMode::KEYS_AND_VALUES);
887
888 // Call before |DidCommit| as delegate can destroy this object.
889 UnloadMapIfPossible();
890
891 delegate_->DidCommit(status);
892 if (callback)
893 std::move(callback).Run();
894 }
895
UnloadMapIfPossible()896 void StorageAreaImpl::UnloadMapIfPossible() {
897 // Do not unload the map if:
898 // * The desired cache mode isn't key-only,
899 // * The map isn't a loaded key-value map,
900 // * There are pending tasks waiting on the key-value map being loaded, or
901 // * There is no database connection.
902 // * We have commit batches in-flight.
903 // * We haven't committed data yet.
904 if (cache_mode_ != CacheMode::KEYS_ONLY_WHEN_POSSIBLE ||
905 map_state_ != MapState::LOADED_KEYS_AND_VALUES ||
906 has_pending_load_tasks() || !database_ || commit_batches_in_flight_ > 0 ||
907 !has_committed_data_) {
908 return;
909 }
910
911 keys_only_map_.clear();
912 memory_used_ = 0;
913 for (auto& it : keys_values_map_) {
914 keys_only_map_.insert(std::make_pair(it.first, it.second.size()));
915 }
916 if (commit_batch_) {
917 for (const auto& key : commit_batch_->changed_keys) {
918 auto value_it = keys_values_map_.find(key);
919 commit_batch_->changed_values[key] = value_it == keys_values_map_.end()
920 ? std::vector<uint8_t>()
921 : std::move(value_it->second);
922 }
923 commit_batch_->changed_keys.clear();
924 }
925
926 keys_values_map_.clear();
927 map_state_ = MapState::LOADED_KEYS_ONLY;
928
929 CalculateStorageAndMemoryUsed();
930 }
931
DoForkOperation(const base::WeakPtr<StorageAreaImpl> & forked_area)932 void StorageAreaImpl::DoForkOperation(
933 const base::WeakPtr<StorageAreaImpl>& forked_area) {
934 if (!forked_area)
935 return;
936
937 DCHECK(IsMapLoaded());
938 // TODO(dmurph): If these commits fails, then the disk could be in an
939 // inconsistant state. Ideally all further operations will fail and the code
940 // will correctly delete the database?
941 if (database_) {
942 // All changes must be stored in the database before the copy operation.
943 if (has_changes_to_commit())
944 CommitChanges();
945 CreateCommitBatchIfNeeded();
946 commit_batch_->copy_to_prefix = forked_area->prefix_;
947 CommitChanges();
948 }
949
950 forked_area->OnForkStateLoaded(database_ != nullptr, keys_values_map_,
951 keys_only_map_);
952 }
953
OnForkStateLoaded(bool database_enabled,const ValueMap & value_map,const KeysOnlyMap & keys_only_map)954 void StorageAreaImpl::OnForkStateLoaded(bool database_enabled,
955 const ValueMap& value_map,
956 const KeysOnlyMap& keys_only_map) {
957 // This callback can get either the value map or the key only map depending
958 // on parent operations and other things. So handle both.
959 if (!value_map.empty() || keys_only_map.empty()) {
960 keys_values_map_ = value_map;
961 map_state_ = MapState::LOADED_KEYS_AND_VALUES;
962 } else {
963 keys_only_map_ = keys_only_map;
964 map_state_ = MapState::LOADED_KEYS_ONLY;
965 }
966
967 if (!database_enabled) {
968 database_ = nullptr;
969 cache_mode_ = CacheMode::KEYS_AND_VALUES;
970 }
971
972 CalculateStorageAndMemoryUsed();
973 OnLoadComplete();
974 }
975
976 } // namespace storage
977