blob: 60a718f3072d2fcb3e46d27608a97cc9ea124597 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/indexed_db/indexed_db_origin_state.h"
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "content/browser/indexed_db/indexed_db_backing_store.h"
#include "content/browser/indexed_db/indexed_db_class_factory.h"
#include "content/browser/indexed_db/indexed_db_connection.h"
#include "content/browser/indexed_db/indexed_db_database.h"
#include "content/browser/indexed_db/indexed_db_factory_impl.h"
#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
#include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
#include "content/browser/indexed_db/indexed_db_pre_close_task_queue.h"
#include "content/browser/indexed_db/indexed_db_tombstone_sweeper.h"
#include "content/browser/indexed_db/indexed_db_transaction.h"
#include "third_party/blink/public/platform/modules/indexeddb/web_idb_database_exception.h"
namespace content {
namespace {
// Time after the last connection to a database is closed and when we destroy
// the backing store.
const int64_t kBackingStoreGracePeriodSeconds = 2;
// Total time we let pre-close tasks run.
const int64_t kRunningPreCloseTasksMaxRunPeriodSeconds = 60;
// The number of iterations for every 'round' of the tombstone sweeper.
const int kTombstoneSweeperRoundIterations = 1000;
// The maximum total iterations for the tombstone sweeper.
const int kTombstoneSweeperMaxIterations = 10 * 1000 * 1000;
constexpr const base::TimeDelta kMinEarliestOriginSweepFromNow =
base::TimeDelta::FromDays(1);
static_assert(kMinEarliestOriginSweepFromNow <
IndexedDBOriginState::kMaxEarliestOriginSweepFromNow,
"Min < Max");
constexpr const base::TimeDelta kMinEarliestGlobalSweepFromNow =
base::TimeDelta::FromMinutes(10);
static_assert(kMinEarliestGlobalSweepFromNow <
IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow,
"Min < Max");
base::Time GenerateNextOriginSweepTime(base::Time now) {
uint64_t range =
IndexedDBOriginState::kMaxEarliestOriginSweepFromNow.InMilliseconds() -
kMinEarliestOriginSweepFromNow.InMilliseconds();
int64_t rand_millis = kMinEarliestOriginSweepFromNow.InMilliseconds() +
static_cast<int64_t>(base::RandGenerator(range));
return now + base::TimeDelta::FromMilliseconds(rand_millis);
}
base::Time GenerateNextGlobalSweepTime(base::Time now) {
uint64_t range =
IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow.InMilliseconds() -
kMinEarliestGlobalSweepFromNow.InMilliseconds();
int64_t rand_millis = kMinEarliestGlobalSweepFromNow.InMilliseconds() +
static_cast<int64_t>(base::RandGenerator(range));
return now + base::TimeDelta::FromMilliseconds(rand_millis);
}
} // namespace
const base::Feature kIDBTombstoneStatistics{"IDBTombstoneStatistics",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kIDBTombstoneDeletion{"IDBTombstoneDeletion",
base::FEATURE_ENABLED_BY_DEFAULT};
constexpr const base::TimeDelta
IndexedDBOriginState::kMaxEarliestGlobalSweepFromNow;
constexpr const base::TimeDelta
IndexedDBOriginState::kMaxEarliestOriginSweepFromNow;
IndexedDBOriginState::IndexedDBOriginState(
bool persist_for_incognito,
base::Clock* clock,
indexed_db::LevelDBFactory* leveldb_factory,
base::Time* earliest_global_sweep_time,
base::OnceClosure destruct_myself,
std::unique_ptr<IndexedDBBackingStore> backing_store)
: persist_for_incognito_(persist_for_incognito),
clock_(clock),
leveldb_factory_(leveldb_factory),
earliest_global_sweep_time_(earliest_global_sweep_time),
lock_manager_(kIndexedDBLockLevelCount),
backing_store_(std::move(backing_store)),
destruct_myself_(std::move(destruct_myself)) {
DCHECK(clock_);
DCHECK(earliest_global_sweep_time_);
if (*earliest_global_sweep_time_ == base::Time())
*earliest_global_sweep_time_ = GenerateNextGlobalSweepTime(clock_->Now());
}
IndexedDBOriginState::~IndexedDBOriginState() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void IndexedDBOriginState::AbortAllTransactions(bool compact) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Because finishing all transactions could cause a database to be destructed
// (which would mutate the database_ map), save the keys beforehand and use
// those.
std::vector<base::string16> origins;
origins.reserve(databases_.size());
for (const auto& pair : databases_) {
origins.push_back(pair.first);
}
for (const base::string16& origin : origins) {
auto it = databases_.find(origin);
if (it == databases_.end())
continue;
// Calling FinishAllTransactions can destruct the IndexedDBConnection &
// modify the IndexedDBDatabase::connection() list. To prevent UAFs, start
// by taking a WeakPtr of all connections, and then iterate that list.
std::vector<base::WeakPtr<IndexedDBConnection>> weak_connections;
weak_connections.reserve(it->second->connections().size());
for (IndexedDBConnection* connection : it->second->connections())
weak_connections.push_back(connection->GetWeakPtr());
for (base::WeakPtr<IndexedDBConnection> connection : weak_connections) {
if (connection) {
connection->FinishAllTransactions(IndexedDBDatabaseError(
blink::kWebIDBDatabaseExceptionUnknownError,
"Aborting all transactions for the origin."));
}
}
}
if (compact)
backing_store_->Compact();
}
void IndexedDBOriginState::ForceClose() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// To avoid re-entry, the |db_destruction_weak_factory_| is invalidated so
// that of the deletions closures returned by CreateDatabaseDeleteClosure will
// no-op. This allows force closing all of the databases without having the
// map mutate. Afterwards the map is manually deleted.
IndexedDBOriginStateHandle handle = CreateHandle();
db_destruction_weak_factory_.InvalidateWeakPtrs();
for (const auto& pair : databases_) {
pair.second->ForceClose();
}
databases_.clear();
if (has_blobs_outstanding_) {
backing_store_->active_blob_registry()->ForceShutdown();
has_blobs_outstanding_ = false;
}
skip_closing_sequence_ = true;
}
void IndexedDBOriginState::ReportOutstandingBlobs(bool blobs_outstanding) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
has_blobs_outstanding_ = blobs_outstanding;
MaybeStartClosing();
}
void IndexedDBOriginState::StopPersistingForIncognito() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
persist_for_incognito_ = false;
MaybeStartClosing();
}
IndexedDBDatabase* IndexedDBOriginState::AddDatabase(
const base::string16& name,
std::unique_ptr<IndexedDBDatabase> database) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::Contains(databases_, name));
return databases_.emplace(name, std::move(database)).first->second.get();
}
base::OnceClosure IndexedDBOriginState::CreateDatabaseDeleteClosure(
const base::string16& name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::BindOnce(
[](base::WeakPtr<IndexedDBOriginState> factory,
const base::string16& name) {
if (!factory)
return;
DCHECK_CALLED_ON_VALID_SEQUENCE(factory->sequence_checker_);
size_t delete_size = factory->databases_.erase(name);
DCHECK(delete_size) << "Database " << name << " did not exist.";
},
db_destruction_weak_factory_.GetWeakPtr(), name);
}
IndexedDBOriginStateHandle IndexedDBOriginState::CreateHandle() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
++open_handles_;
if (closing_stage_ != ClosingState::kNotClosing) {
closing_stage_ = ClosingState::kNotClosing;
close_timer_.AbandonAndStop();
if (pre_close_task_queue_) {
pre_close_task_queue_->StopForNewConnection();
pre_close_task_queue_.reset();
}
}
return IndexedDBOriginStateHandle(weak_factory_.GetWeakPtr());
}
void IndexedDBOriginState::OnHandleDestruction() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(open_handles_, 0ll);
--open_handles_;
MaybeStartClosing();
}
bool IndexedDBOriginState::CanCloseFactory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(open_handles_, 0);
return !has_blobs_outstanding_ && open_handles_ <= 0 &&
!persist_for_incognito_;
}
void IndexedDBOriginState::MaybeStartClosing() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsClosing() && CanCloseFactory())
StartClosing();
}
void IndexedDBOriginState::StartClosing() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(CanCloseFactory());
DCHECK(!IsClosing());
if (skip_closing_sequence_ ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
kIDBCloseImmediatelySwitch)) {
closing_stage_ = ClosingState::kClosed;
CloseAndDestruct();
return;
}
// Start a timer to close the backing store, unless something else opens it
// in the mean time.
DCHECK(!close_timer_.IsRunning());
closing_stage_ = ClosingState::kPreCloseGracePeriod;
close_timer_.Start(
FROM_HERE, base::TimeDelta::FromSeconds(kBackingStoreGracePeriodSeconds),
base::BindOnce(
[](base::WeakPtr<IndexedDBOriginState> factory) {
if (!factory ||
factory->closing_stage_ != ClosingState::kPreCloseGracePeriod)
return;
factory->StartPreCloseTasks();
},
weak_factory_.GetWeakPtr()));
}
void IndexedDBOriginState::StartPreCloseTasks() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(closing_stage_ == ClosingState::kPreCloseGracePeriod);
closing_stage_ = ClosingState::kRunningPreCloseTasks;
// The callback will run on all early returns in this function.
base::ScopedClosureRunner maybe_close_backing_store_runner(base::BindOnce(
[](base::WeakPtr<IndexedDBOriginState> factory) {
if (!factory ||
factory->closing_stage_ != ClosingState::kRunningPreCloseTasks)
return;
factory->closing_stage_ = ClosingState::kClosed;
factory->pre_close_task_queue_.reset();
factory->CloseAndDestruct();
},
weak_factory_.GetWeakPtr()));
base::Time now = clock_->Now();
// Check that the last sweep hasn't run too recently.
if (*earliest_global_sweep_time_ > now)
return;
bool tombstone_stats_enabled =
base::FeatureList::IsEnabled(kIDBTombstoneStatistics);
bool tombstone_deletion_enabled =
base::FeatureList::IsEnabled(kIDBTombstoneDeletion);
// After this check, exactly one of the flags must be true.
if (tombstone_stats_enabled == tombstone_deletion_enabled)
return;
base::Time origin_earliest_sweep;
leveldb::Status s = indexed_db::GetEarliestSweepTime(backing_store_->db(),
&origin_earliest_sweep);
// TODO(dmurph): Log this or report to UMA.
if (!s.ok() && !s.IsNotFound())
return;
// This origin hasn't been swept too recently.
if (origin_earliest_sweep > now)
return;
// A sweep will happen now, so reset the sweep timers.
*earliest_global_sweep_time_ = GenerateNextGlobalSweepTime(now);
scoped_refptr<LevelDBTransaction> txn =
leveldb_factory_->CreateLevelDBTransaction(backing_store_->db());
indexed_db::SetEarliestSweepTime(txn.get(), GenerateNextOriginSweepTime(now));
s = txn->Commit();
// TODO(dmurph): Log this or report to UMA.
if (!s.ok())
return;
std::list<std::unique_ptr<IndexedDBPreCloseTaskQueue::PreCloseTask>> tasks;
IndexedDBTombstoneSweeper::Mode mode =
tombstone_stats_enabled ? IndexedDBTombstoneSweeper::Mode::STATISTICS
: IndexedDBTombstoneSweeper::Mode::DELETION;
tasks.push_back(std::make_unique<IndexedDBTombstoneSweeper>(
mode, kTombstoneSweeperRoundIterations, kTombstoneSweeperMaxIterations,
backing_store_->db()->db()));
// TODO(dmurph): Add compaction task that compacts all indexes if we have
// more than X deletions.
pre_close_task_queue_ = std::make_unique<IndexedDBPreCloseTaskQueue>(
std::move(tasks), maybe_close_backing_store_runner.Release(),
base::TimeDelta::FromSeconds(kRunningPreCloseTasksMaxRunPeriodSeconds),
std::make_unique<base::OneShotTimer>());
pre_close_task_queue_->Start(
base::BindOnce(&IndexedDBBackingStore::GetCompleteMetadata,
base::Unretained(backing_store_.get())));
}
void IndexedDBOriginState::CloseAndDestruct() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(CanCloseFactory());
DCHECK(closing_stage_ == ClosingState::kClosed);
close_timer_.AbandonAndStop();
pre_close_task_queue_.reset();
if (backing_store_ && backing_store_->IsBlobCleanupPending())
backing_store_->ForceRunBlobCleanup();
std::move(destruct_myself_).Run();
}
} // namespace content