blob: 416401a41825fbd14494faa923ededa4ef553a67 [file] [log] [blame]
// Copyright (c) 2013 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_factory_impl.h"
#include <stdint.h>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/timer/timer.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_context_impl.h"
#include "content/browser/indexed_db/indexed_db_database_error.h"
#include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
#include "content/browser/indexed_db/indexed_db_metadata_coding.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_tracing.h"
#include "third_party/blink/public/platform/modules/indexeddb/web_idb_database_exception.h"
#include "third_party/leveldatabase/env_chromium.h"
using base::ASCIIToUTF16;
using url::Origin;
namespace content {
namespace {
using PreCloseTask = IndexedDBPreCloseTaskQueue::PreCloseTask;
// 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 kPreCloseTasksMaxRunPeriodSeconds = 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 <
IndexedDBFactoryImpl::kMaxEarliestOriginSweepFromNow,
"Min < Max");
constexpr const base::TimeDelta kMinEarliestGlobalSweepFromNow =
base::TimeDelta::FromMinutes(10);
static_assert(kMinEarliestGlobalSweepFromNow <
IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow,
"Min < Max");
base::Time GenerateNextOriginSweepTime(base::Time now) {
uint64_t range =
IndexedDBFactoryImpl::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 =
IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow.InMilliseconds() -
kMinEarliestGlobalSweepFromNow.InMilliseconds();
int64_t rand_millis = kMinEarliestGlobalSweepFromNow.InMilliseconds() +
static_cast<int64_t>(base::RandGenerator(range));
return now + base::TimeDelta::FromMilliseconds(rand_millis);
}
leveldb::Status GetDBSizeFromEnv(leveldb::Env* env,
const std::string& path,
int64_t* total_size_out) {
*total_size_out = 0;
// Root path should be /, but in MemEnv, a path name is not tailed with '/'
DCHECK_EQ(path.back(), '/');
const std::string path_without_slash = path.substr(0, path.length() - 1);
// This assumes that leveldb will not put a subdirectory into the directory
std::vector<std::string> file_names;
leveldb::Status s = env->GetChildren(path_without_slash, &file_names);
if (!s.ok())
return s;
for (std::string& file_name : file_names) {
file_name.insert(0, path);
uint64_t file_size;
s = env->GetFileSize(file_name, &file_size);
if (!s.ok())
return s;
else
*total_size_out += static_cast<int64_t>(file_size);
}
return s;
}
} // 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
IndexedDBFactoryImpl::kMaxEarliestGlobalSweepFromNow;
constexpr const base::TimeDelta
IndexedDBFactoryImpl::kMaxEarliestOriginSweepFromNow;
IndexedDBFactoryImpl::IndexedDBFactoryImpl(
IndexedDBContextImpl* context,
indexed_db::LevelDBFactory* leveldb_factory,
base::Clock* clock)
: context_(context),
leveldb_factory_(leveldb_factory),
clock_(clock),
earliest_sweep_(GenerateNextGlobalSweepTime(clock_->Now())) {}
IndexedDBFactoryImpl::~IndexedDBFactoryImpl() {
}
void IndexedDBFactoryImpl::ReleaseDatabase(
const IndexedDBDatabase::Identifier& identifier,
bool forced_close) {
DCHECK(!database_map_.find(identifier)->second->backing_store());
RemoveDatabaseFromMaps(identifier);
// No grace period on a forced-close, as the initiator is
// assuming the backing store will be released once all
// connections are closed.
ReleaseBackingStore(identifier.first, forced_close);
}
void IndexedDBFactoryImpl::GetDatabaseInfo(
scoped_refptr<IndexedDBCallbacks> callbacks,
const Origin& origin,
const base::FilePath& data_directory) {
IDB_TRACE("IndexedDBFactoryImpl::GetDatabaseInfo");
// TODO(dmurph): Plumb data_loss back to script eventually?
IndexedDBDataLossInfo data_loss_info;
bool disk_full;
leveldb::Status s;
scoped_refptr<IndexedDBBackingStore> backing_store;
std::tie(backing_store, s, data_loss_info, disk_full) =
OpenBackingStore(origin, data_directory);
if (!backing_store.get()) {
IndexedDBDatabaseError error(
blink::kWebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16("Internal error opening backing store for "
"indexedDB.databases()."));
callbacks->OnError(error);
if (s.IsCorruption())
HandleBackingStoreCorruption(origin, error);
return;
}
IndexedDBMetadataCoding metadata_coding;
std::vector<blink::mojom::IDBNameAndVersionPtr> names_and_versions;
s = metadata_coding.ReadDatabaseNamesAndVersions(
backing_store->db(), backing_store->origin_identifier(),
&names_and_versions);
if (!s.ok()) {
DLOG(ERROR) << "Internal error getting database info";
IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError,
"Internal error opening backing store for "
"indexedDB.databases().");
callbacks->OnError(error);
backing_store = nullptr;
if (s.IsCorruption())
HandleBackingStoreCorruption(origin, error);
return;
}
callbacks->OnSuccess(std::move(names_and_versions));
backing_store = nullptr;
ReleaseBackingStore(origin, false /* immediate */);
}
void IndexedDBFactoryImpl::GetDatabaseNames(
scoped_refptr<IndexedDBCallbacks> callbacks,
const Origin& origin,
const base::FilePath& data_directory) {
IDB_TRACE("IndexedDBFactoryImpl::GetDatabaseNames");
// TODO(dmurph): Plumb data_loss back to script eventually?
IndexedDBDataLossInfo data_loss_info;
bool disk_full;
leveldb::Status s;
scoped_refptr<IndexedDBBackingStore> backing_store;
std::tie(backing_store, s, data_loss_info, disk_full) =
OpenBackingStore(origin, data_directory);
if (!backing_store.get()) {
IndexedDBDatabaseError error(
blink::kWebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16("Internal error opening backing store for "
"indexedDB.webkitGetDatabaseNames."));
callbacks->OnError(error);
if (s.IsCorruption())
HandleBackingStoreCorruption(origin, error);
return;
}
IndexedDBMetadataCoding metadata_coding;
std::vector<base::string16> names;
s = metadata_coding.ReadDatabaseNames(
backing_store->db(), backing_store->origin_identifier(), &names);
if (!s.ok()) {
DLOG(ERROR) << "Internal error getting database names";
IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError,
"Internal error opening backing store for "
"indexedDB.webkitGetDatabaseNames.");
callbacks->OnError(error);
backing_store = nullptr;
if (s.IsCorruption())
HandleBackingStoreCorruption(origin, error);
return;
}
callbacks->OnSuccess(names);
backing_store = nullptr;
ReleaseBackingStore(origin, false /* immediate */);
}
void IndexedDBFactoryImpl::Open(
const base::string16& name,
std::unique_ptr<IndexedDBPendingConnection> connection,
const Origin& origin,
const base::FilePath& data_directory) {
IDB_TRACE("IndexedDBFactoryImpl::Open");
IndexedDBDatabase::Identifier unique_identifier(origin, name);
auto it = database_map_.find(unique_identifier);
if (it != database_map_.end()) {
it->second->OpenConnection(std::move(connection));
return;
}
leveldb::Status s;
scoped_refptr<IndexedDBBackingStore> backing_store;
bool disk_full;
std::tie(backing_store, s, connection->data_loss_info, disk_full) =
OpenBackingStore(origin, data_directory);
if (!backing_store) {
if (disk_full) {
connection->callbacks->OnError(IndexedDBDatabaseError(
blink::kWebIDBDatabaseExceptionQuotaError,
ASCIIToUTF16("Encountered full disk while opening "
"backing store for indexedDB.open.")));
return;
}
IndexedDBDatabaseError error(
blink::kWebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16("Internal error opening backing store"
" for indexedDB.open."));
connection->callbacks->OnError(error);
if (s.IsCorruption()) {
HandleBackingStoreCorruption(origin, error);
}
return;
}
scoped_refptr<IndexedDBDatabase> database;
std::tie(database, s) = IndexedDBDatabase::Create(
name, backing_store.get(), this,
std::make_unique<IndexedDBMetadataCoding>(), unique_identifier,
backing_store->lock_manager());
if (!database.get()) {
DLOG(ERROR) << "Unable to create the database";
IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16("Internal error creating "
"database backend for "
"indexedDB.open."));
connection->callbacks->OnError(error);
if (s.IsCorruption()) {
backing_store = nullptr; // Closes the LevelDB so that it can be deleted
HandleBackingStoreCorruption(origin, error);
}
return;
}
database->OpenConnection(std::move(connection));
if (database->ConnectionCount() > 0) {
database_map_[unique_identifier] = database.get();
origin_dbs_.insert(std::make_pair(origin, database.get()));
}
}
void IndexedDBFactoryImpl::DeleteDatabase(
const base::string16& name,
scoped_refptr<IndexedDBCallbacks> callbacks,
const Origin& origin,
const base::FilePath& data_directory,
bool force_close) {
IDB_TRACE("IndexedDBFactoryImpl::DeleteDatabase");
IndexedDBDatabase::Identifier unique_identifier(origin, name);
auto it = database_map_.find(unique_identifier);
if (it != database_map_.end()) {
// If there are any connections to the database, directly delete the
// database.
it->second->DeleteDatabase(callbacks, force_close);
return;
}
// TODO(dmurph): Plumb data_loss back to script eventually?
IndexedDBDataLossInfo data_loss_info;
bool disk_full;
leveldb::Status s;
scoped_refptr<IndexedDBBackingStore> backing_store;
std::tie(backing_store, s, data_loss_info, disk_full) =
OpenBackingStore(origin, data_directory);
if (!backing_store.get()) {
IndexedDBDatabaseError error(
blink::kWebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16("Internal error opening backing store "
"for indexedDB.deleteDatabase."));
callbacks->OnError(error);
if (s.IsCorruption()) {
HandleBackingStoreCorruption(origin, error);
}
return;
}
IndexedDBMetadataCoding metadata_coding;
std::vector<base::string16> names;
s = metadata_coding.ReadDatabaseNames(
backing_store->db(), backing_store->origin_identifier(), &names);
if (!s.ok()) {
DLOG(ERROR) << "Internal error getting database names";
IndexedDBDatabaseError error(blink::kWebIDBDatabaseExceptionUnknownError,
"Internal error opening backing store for "
"indexedDB.deleteDatabase.");
callbacks->OnError(error);
backing_store = nullptr;
if (s.IsCorruption())
HandleBackingStoreCorruption(origin, error);
return;
}
if (!base::ContainsValue(names, name)) {
const int64_t version = 0;
callbacks->OnSuccess(version);
backing_store = nullptr;
ReleaseBackingStore(origin, false /* immediate */);
return;
}
scoped_refptr<IndexedDBDatabase> database;
std::tie(database, s) = IndexedDBDatabase::Create(
name, backing_store.get(), this,
std::make_unique<IndexedDBMetadataCoding>(), unique_identifier,
backing_store->lock_manager());
if (!database.get()) {
IndexedDBDatabaseError error(
blink::kWebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16("Internal error creating database backend for "
"indexedDB.deleteDatabase."));
callbacks->OnError(error);
if (s.IsCorruption()) {
backing_store = nullptr;
HandleBackingStoreCorruption(origin, error);
}
return;
}
database_map_[unique_identifier] = database.get();
origin_dbs_.insert(std::make_pair(origin, database.get()));
database->DeleteDatabase(callbacks, force_close);
RemoveDatabaseFromMaps(unique_identifier);
database = nullptr;
backing_store = nullptr;
ReleaseBackingStore(origin, false /* immediate */);
}
void IndexedDBFactoryImpl::AbortTransactionsAndCompactDatabase(
base::OnceCallback<void(leveldb::Status)> callback,
const Origin& origin) {
IDB_TRACE("IndexedDBFactoryImpl::AbortTransactionsAndCompactDatabase");
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end()) {
std::move(callback).Run(leveldb::Status::IOError(
"Internal error opening backing store for "
"indexedDB.abortTransactionsAndCompactDatabase."));
return;
}
IndexedDBBackingStore* backing_store = it->second.get();
leveldb::Status status = AbortTransactions(origin);
backing_store->Compact();
std::move(callback).Run(status);
}
void IndexedDBFactoryImpl::AbortTransactionsForDatabase(
base::OnceCallback<void(leveldb::Status)> callback,
const Origin& origin) {
IDB_TRACE("IndexedDBFactoryImpl::AbortTransactionsForDatabase");
if (!base::ContainsKey(backing_store_map_, origin)) {
std::move(callback).Run(
leveldb::Status::IOError("Internal error opening backing store for "
"indexedDB.abortTransactionsForDatabase."));
return;
}
std::move(callback).Run(AbortTransactions(origin));
}
void IndexedDBFactoryImpl::HandleBackingStoreFailure(const Origin& origin) {
// NULL after ContextDestroyed() called, and in some unit tests.
if (!context_)
return;
context_->ForceClose(origin,
IndexedDBContextImpl::FORCE_CLOSE_BACKING_STORE_FAILURE);
}
void IndexedDBFactoryImpl::HandleBackingStoreCorruption(
const Origin& origin,
const IndexedDBDatabaseError& error) {
// Make a copy of origin as this is likely a reference to a member of a
// backing store which this function will be deleting.
Origin saved_origin(origin);
DCHECK(context_);
base::FilePath path_base = context_->data_path();
// The message may contain the database path, which may be considered
// sensitive data, and those strings are passed to the extension, so strip it.
std::string sanitized_message = base::UTF16ToUTF8(error.message());
base::ReplaceSubstringsAfterOffset(&sanitized_message, 0u,
path_base.AsUTF8Unsafe(), "...");
IndexedDBBackingStore::RecordCorruptionInfo(path_base, saved_origin,
sanitized_message);
HandleBackingStoreFailure(saved_origin);
// Note: DestroyBackingStore only deletes LevelDB files, leaving all others,
// so our corruption info file will remain.
const base::FilePath file_path =
path_base.Append(indexed_db::GetLevelDBFileName(saved_origin));
leveldb::Status s = leveldb_factory_->DestroyLevelDB(file_path);
DLOG_IF(ERROR, !s.ok()) << "Unable to delete backing store: " << s.ToString();
UMA_HISTOGRAM_ENUMERATION(
"WebCore.IndexedDB.DestroyCorruptBackingStoreStatus",
leveldb_env::GetLevelDBStatusUMAValue(s),
leveldb_env::LEVELDB_STATUS_MAX);
}
std::pair<IndexedDBFactoryImpl::OriginDBMapIterator,
IndexedDBFactoryImpl::OriginDBMapIterator>
IndexedDBFactoryImpl::GetOpenDatabasesForOrigin(const Origin& origin) const {
return origin_dbs_.equal_range(origin);
}
void IndexedDBFactoryImpl::ForceClose(const Origin& origin,
bool delete_in_memory_store) {
OriginDBs range = GetOpenDatabasesForOrigin(origin);
while (range.first != range.second) {
IndexedDBDatabase* db = range.first->second;
++range.first;
db->ForceClose();
}
auto it = backing_store_map_.find(origin);
if (it != backing_store_map_.end()) {
if (delete_in_memory_store)
in_memory_backing_stores_.erase(it->second);
ReleaseBackingStore(origin, true /* immediate */);
}
}
void IndexedDBFactoryImpl::ForceSchemaDowngrade(const Origin& origin) {
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end())
return;
IndexedDBBackingStore* backing_store = it->second.get();
leveldb::Status s = backing_store->RevertSchemaToV2();
DLOG_IF(ERROR, !s.ok()) << "Unable to force downgrade: " << s.ToString();
}
V2SchemaCorruptionStatus IndexedDBFactoryImpl::HasV2SchemaCorruption(
const Origin& origin) {
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end())
return V2SchemaCorruptionStatus::kUnknown;
IndexedDBBackingStore* backing_store = it->second.get();
return backing_store->HasV2SchemaCorruption();
}
void IndexedDBFactoryImpl::ContextDestroyed() {
// Timers on backing stores hold a reference to this factory. When the
// context (which nominally owns this factory) is destroyed during thread
// termination the timers must be stopped so that this factory and the
// stores can be disposed of.
for (const auto& origin_backing_store_pair : backing_store_map_) {
origin_backing_store_pair.second->close_timer()->Stop();
origin_backing_store_pair.second->SetPreCloseTaskList(nullptr);
}
backing_store_map_.clear();
backing_stores_with_active_blobs_.clear();
context_ = nullptr;
}
void IndexedDBFactoryImpl::ReportOutstandingBlobs(const Origin& origin,
bool blobs_outstanding) {
if (!context_)
return;
if (blobs_outstanding) {
DCHECK(!backing_stores_with_active_blobs_.count(origin));
auto it = backing_store_map_.find(origin);
if (it != backing_store_map_.end())
backing_stores_with_active_blobs_.insert(*it);
else
DCHECK(false);
} else {
auto it = backing_stores_with_active_blobs_.find(origin);
if (it != backing_stores_with_active_blobs_.end()) {
backing_stores_with_active_blobs_.erase(it);
ReleaseBackingStore(origin, false /* immediate */);
}
}
}
void IndexedDBFactoryImpl::DatabaseDeleted(
const IndexedDBDatabase::Identifier& identifier) {
// NULL after ContextDestroyed() called, and in some unit tests.
if (!context_)
return;
context_->DatabaseDeleted(identifier.first);
}
void IndexedDBFactoryImpl::BlobFilesCleaned(const url::Origin& origin) {
// NULL after ContextDestroyed() called, and in some unit tests.
if (!context_)
return;
context_->BlobFilesCleaned(origin);
}
size_t IndexedDBFactoryImpl::GetConnectionCount(const Origin& origin) const {
size_t count(0);
OriginDBs range = GetOpenDatabasesForOrigin(origin);
for (auto it = range.first; it != range.second; ++it)
count += it->second->ConnectionCount();
return count;
}
void IndexedDBFactoryImpl::NotifyIndexedDBContentChanged(
const url::Origin& origin,
const base::string16& database_name,
const base::string16& object_store_name) {
if (!context_)
return;
context_->NotifyIndexedDBContentChanged(origin, database_name,
object_store_name);
}
int64_t IndexedDBFactoryImpl::GetInMemoryDBSize(const Origin& origin) const {
auto it = backing_store_map_.find(origin);
// Origin won't be present in map if it has been deleted.
if (it == backing_store_map_.end())
return 0;
IndexedDBBackingStore* backing_store = it->second.get();
int64_t level_db_size = 0;
leveldb::Status s =
GetDBSizeFromEnv(backing_store->db()->env(), "/", &level_db_size);
if (!s.ok())
LOG(ERROR) << "Failed to GetDBSizeFromEnv: " << s.ToString();
return backing_store->GetInMemoryBlobSize() + level_db_size;
}
base::Time IndexedDBFactoryImpl::GetLastModified(
const url::Origin& origin) const {
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end())
return base::Time();
IndexedDBBackingStore* backing_store = it->second.get();
return backing_store->db()->LastModified();
}
std::tuple<scoped_refptr<IndexedDBBackingStore>,
leveldb::Status,
IndexedDBDataLossInfo,
bool /* disk_full */>
IndexedDBFactoryImpl::OpenBackingStore(const Origin& origin,
const base::FilePath& data_directory) {
auto it2 = backing_store_map_.find(origin);
scoped_refptr<IndexedDBBackingStore> backing_store;
if (it2 != backing_store_map_.end()) {
// Grab a refptr so the completion of the preclose task list doesn't close
// the backing store.
backing_store = it2->second;
backing_store->close_timer()->Stop();
if (it2->second->pre_close_task_queue()) {
backing_store->pre_close_task_queue()->StopForNewConnection();
backing_store->SetPreCloseTaskList(nullptr);
}
return {std::move(backing_store), leveldb::Status::OK(),
IndexedDBDataLossInfo(), false};
}
base::FilePath blob_path;
base::FilePath database_path;
leveldb::Status s;
if (!data_directory.empty()) {
// The database will be on-disk and not in-memory.
std::tie(database_path, blob_path, s) =
indexed_db::CreateDatabaseDirectories(data_directory, origin);
if (!s.ok())
return {std::move(backing_store), s, IndexedDBDataLossInfo(), false};
}
std::unique_ptr<LevelDBDatabase> database;
IndexedDBDataLossInfo data_loss_info;
bool disk_full;
std::tie(database, s, data_loss_info, disk_full) =
indexed_db::OpenAndVerifyLevelDBDatabase(origin, data_directory,
database_path, leveldb_factory_,
context_->TaskRunner());
if (!s.ok())
return {std::move(backing_store), s, data_loss_info, disk_full};
IndexedDBBackingStore::Mode backing_store_mode =
data_directory.empty() ? IndexedDBBackingStore::Mode::kInMemory
: IndexedDBBackingStore::Mode::kOnDisk;
backing_store =
CreateBackingStore(backing_store_mode, origin, blob_path,
std::move(database), context_->TaskRunner());
bool first_open_since_startup =
backends_opened_since_startup_.insert(origin).second;
s = backing_store->Initialize(
/*cleanup_live_journal=*/!database_path.empty() &&
first_open_since_startup);
if (!s.ok()) {
backing_store.reset();
return {std::move(backing_store), s, data_loss_info, disk_full};
}
// If an in-memory database, bind lifetime to this factory instance.
if (database_path.empty())
in_memory_backing_stores_.insert(backing_store);
backing_store_map_[origin] = backing_store;
// All backing stores associated with this factory should be of the same
// type.
DCHECK_EQ(!in_memory_backing_stores_.empty(), database_path.empty());
DCHECK(backing_store);
return {std::move(backing_store), s, data_loss_info, disk_full};
}
scoped_refptr<IndexedDBBackingStore> IndexedDBFactoryImpl::CreateBackingStore(
IndexedDBBackingStore::Mode backing_store_mode,
const url::Origin& origin,
const base::FilePath& blob_path,
std::unique_ptr<LevelDBDatabase> db,
base::SequencedTaskRunner* task_runner) {
return base::MakeRefCounted<IndexedDBBackingStore>(
backing_store_mode, this, origin, blob_path, std::move(db), task_runner);
}
void IndexedDBFactoryImpl::ReleaseBackingStore(const Origin& origin,
bool immediate) {
if (!base::ContainsKey(backing_store_map_, origin))
return;
if (immediate) {
auto it = backing_stores_with_active_blobs_.find(origin);
if (it != backing_stores_with_active_blobs_.end()) {
it->second->active_blob_registry()->ForceShutdown();
backing_stores_with_active_blobs_.erase(it);
}
}
// Only close if this is the last reference.
if (!HasLastBackingStoreReference(origin))
return;
// If this factory does hold the last reference to the backing store, it can
// be closed - but unless requested to close it immediately, keep it around
// for a short period so that a re-open is fast.
if (immediate) {
CloseBackingStore(origin);
return;
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
kIDBCloseImmediatelySwitch)) {
MaybeCloseBackingStore(origin);
return;
}
// Start a timer to close the backing store, unless something else opens it
// in the mean time.
DCHECK(!backing_store_map_[origin]->close_timer()->IsRunning());
backing_store_map_[origin]->close_timer()->Start(
FROM_HERE, base::TimeDelta::FromSeconds(kBackingStoreGracePeriodSeconds),
base::BindOnce(&IndexedDBFactoryImpl::MaybeStartPreCloseTasks, this,
origin));
}
void IndexedDBFactoryImpl::CloseBackingStore(const Origin& origin) {
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end())
return;
// Stop the timer and pre close tasks (if they are running) - this may happen
// if the timer was started and then a forced close occurs.
IndexedDBBackingStore* backing_store = it->second.get();
backing_store->close_timer()->Stop();
backing_store->SetPreCloseTaskList(nullptr);
if (backing_store->IsBlobCleanupPending())
backing_store->ForceRunBlobCleanup();
backing_store_map_.erase(it);
}
leveldb::Status IndexedDBFactoryImpl::AbortTransactions(const Origin& origin) {
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end()) {
return leveldb::Status::IOError(
"Internal error opening backing store for "
"indexedDB.abortTransactions.");
}
IndexedDBBackingStore* backing_store = it->second.get();
leveldb::Status get_names_status;
IndexedDBMetadataCoding metadata_coding;
std::vector<base::string16> db_names;
get_names_status = metadata_coding.ReadDatabaseNames(
backing_store->db(), backing_store->origin_identifier(), &db_names);
if (!get_names_status.ok()) {
return leveldb::Status::IOError(
"Internal error getting origin database names for "
"indexedDB.abortTransactions.");
}
for (base::string16& name : db_names) {
auto database_it = database_map_.find(std::make_pair(origin, name));
if (database_it != database_map_.end())
database_it->second->AbortAllTransactionsForConnections();
}
return leveldb::Status::OK();
}
void IndexedDBFactoryImpl::MaybeStartPreCloseTasks(const Origin& origin) {
if (!base::ContainsKey(backing_store_map_, origin))
return;
// Another reference may have been created since the maybe-close was posted,
// so it is necessary to check again.
if (!HasLastBackingStoreReference(origin))
return;
base::ScopedClosureRunner maybe_close_backing_store_runner(
base::BindOnce(&IndexedDBFactoryImpl::MaybeCloseBackingStore,
base::Unretained(this), origin));
base::Time now = clock_->Now();
// Check that the last sweep hasn't run too recently.
if (earliest_sweep_ > 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;
scoped_refptr<IndexedDBBackingStore> store = backing_store_map_[origin];
base::Time origin_earliest_sweep;
leveldb::Status s =
indexed_db::GetEarliestSweepTime(store->db(), &origin_earliest_sweep);
// TODO(dmurph): Log this or report to UMA.
if (!s.ok())
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_sweep_ = GenerateNextGlobalSweepTime(now);
scoped_refptr<LevelDBTransaction> txn =
IndexedDBClassFactory::Get()->CreateLevelDBTransaction(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<PreCloseTask>> tasks;
IndexedDBTombstoneSweeper::Mode mode =
tombstone_stats_enabled ? IndexedDBTombstoneSweeper::Mode::STATISTICS
: IndexedDBTombstoneSweeper::Mode::DELETION;
tasks.push_back(std::make_unique<IndexedDBTombstoneSweeper>(
mode, kTombstoneSweeperRoundIterations, kTombstoneSweeperMaxIterations,
store->db()->db()));
// TODO(dmurph): Add compaction task that compacts all indexes if we have
// more than X deletions.
store->SetPreCloseTaskList(std::make_unique<IndexedDBPreCloseTaskQueue>(
std::move(tasks), maybe_close_backing_store_runner.Release(),
base::TimeDelta::FromSeconds(kPreCloseTasksMaxRunPeriodSeconds),
std::make_unique<base::OneShotTimer>()));
store->StartPreCloseTasks();
}
void IndexedDBFactoryImpl::MaybeCloseBackingStore(const Origin& origin) {
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end())
return;
IndexedDBBackingStore* backing_store = it->second.get();
backing_store->SetPreCloseTaskList(nullptr);
// Another reference may have opened since the maybe-close was posted, so it
// is necessary to check again.
if (HasLastBackingStoreReference(origin))
CloseBackingStore(origin);
}
bool IndexedDBFactoryImpl::HasLastBackingStoreReference(
const Origin& origin) const {
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end())
return false;
return it->second->HasOneRef();
}
bool IndexedDBFactoryImpl::IsDatabaseOpen(const Origin& origin,
const base::string16& name) const {
return base::ContainsKey(database_map_,
IndexedDBDatabase::Identifier(origin, name));
}
bool IndexedDBFactoryImpl::IsBackingStoreOpen(const Origin& origin) const {
return base::ContainsKey(backing_store_map_, origin);
}
bool IndexedDBFactoryImpl::IsBackingStorePendingClose(
const Origin& origin) const {
auto it = backing_store_map_.find(origin);
if (it == backing_store_map_.end())
return false;
return it->second->close_timer()->IsRunning() ||
it->second->pre_close_task_queue();
}
void IndexedDBFactoryImpl::RemoveDatabaseFromMaps(
const IndexedDBDatabase::Identifier& identifier) {
auto it = database_map_.find(identifier);
DCHECK(it != database_map_.end());
IndexedDBDatabase* database = it->second;
database_map_.erase(it);
std::pair<OriginDBMap::iterator, OriginDBMap::iterator> range =
origin_dbs_.equal_range(database->identifier().first);
DCHECK(range.first != range.second);
for (auto it2 = range.first; it2 != range.second; ++it2) {
if (it2->second == database) {
origin_dbs_.erase(it2);
break;
}
}
}
} // namespace content