| // 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 "sqlite_persistent_store_backend_base.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "sql/database.h" |
| #include "sql/error_delegate_util.h" |
| |
| namespace net { |
| |
| SQLitePersistentStoreBackendBase::SQLitePersistentStoreBackendBase( |
| const base::FilePath& path, |
| std::string histogram_tag, |
| const int current_version_number, |
| const int compatible_version_number, |
| scoped_refptr<base::SequencedTaskRunner> background_task_runner, |
| scoped_refptr<base::SequencedTaskRunner> client_task_runner) |
| : path_(path), |
| histogram_tag_(std::move(histogram_tag)), |
| initialized_(false), |
| corruption_detected_(false), |
| current_version_number_(current_version_number), |
| compatible_version_number_(compatible_version_number), |
| background_task_runner_(std::move(background_task_runner)), |
| client_task_runner_(std::move(client_task_runner)) {} |
| |
| SQLitePersistentStoreBackendBase::~SQLitePersistentStoreBackendBase() { |
| DCHECK(!db_.get()) << "Close should already have been called."; |
| } |
| |
| void SQLitePersistentStoreBackendBase::Flush(base::OnceClosure callback) { |
| DCHECK(!background_task_runner_->RunsTasksInCurrentSequence()); |
| PostBackgroundTask( |
| FROM_HERE, |
| base::BindOnce( |
| &SQLitePersistentStoreBackendBase::FlushAndNotifyInBackground, this, |
| std::move(callback))); |
| } |
| |
| void SQLitePersistentStoreBackendBase::Close() { |
| if (background_task_runner_->RunsTasksInCurrentSequence()) { |
| DoCloseInBackground(); |
| } else { |
| // Must close the backend on the background runner. |
| PostBackgroundTask( |
| FROM_HERE, |
| base::BindOnce(&SQLitePersistentStoreBackendBase::DoCloseInBackground, |
| this)); |
| } |
| } |
| |
| void SQLitePersistentStoreBackendBase::SetBeforeCommitCallback( |
| base::RepeatingClosure callback) { |
| base::AutoLock locked(before_commit_callback_lock_); |
| before_commit_callback_ = std::move(callback); |
| } |
| |
| bool SQLitePersistentStoreBackendBase::InitializeDatabase() { |
| DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (initialized_ || corruption_detected_) { |
| // Return false if we were previously initialized but the DB has since been |
| // closed, or if corruption caused a database reset during initialization. |
| return db_ != nullptr; |
| } |
| |
| base::Time start = base::Time::Now(); |
| |
| const base::FilePath dir = path_.DirName(); |
| if (!base::PathExists(dir) && !base::CreateDirectory(dir)) { |
| RecordPathDoesNotExistProblem(); |
| return false; |
| } |
| |
| db_ = std::make_unique<sql::Database>(); |
| db_->set_histogram_tag(histogram_tag_); |
| |
| // base::Unretained is safe because |this| owns (and therefore outlives) the |
| // sql::Database held by |db_|. |
| db_->set_error_callback(base::BindRepeating( |
| &SQLitePersistentStoreBackendBase::DatabaseErrorCallback, |
| base::Unretained(this))); |
| |
| bool new_db = !base::PathExists(path_); |
| |
| if (!db_->Open(path_)) { |
| DLOG(ERROR) << "Unable to open " << histogram_tag_ << " DB."; |
| RecordOpenDBProblem(); |
| Reset(); |
| return false; |
| } |
| db_->Preload(); |
| |
| if (!MigrateDatabaseSchema() || !CreateDatabaseSchema()) { |
| DLOG(ERROR) << "Unable to update or initialize " << histogram_tag_ |
| << " DB tables."; |
| RecordDBMigrationProblem(); |
| Reset(); |
| return false; |
| } |
| |
| base::UmaHistogramCustomTimes(histogram_tag_ + ".TimeInitializeDB", |
| base::Time::Now() - start, |
| base::TimeDelta::FromMilliseconds(1), |
| base::TimeDelta::FromMinutes(1), 50); |
| |
| initialized_ = DoInitializeDatabase(); |
| |
| if (!initialized_) { |
| DLOG(ERROR) << "Unable to initialize " << histogram_tag_ << " DB."; |
| RecordOpenDBProblem(); |
| Reset(); |
| return false; |
| } |
| |
| if (new_db) { |
| RecordNewDBFile(); |
| } else { |
| RecordDBLoaded(); |
| } |
| |
| return true; |
| } |
| |
| bool SQLitePersistentStoreBackendBase::DoInitializeDatabase() { |
| return true; |
| } |
| |
| void SQLitePersistentStoreBackendBase::Reset() { |
| if (db_ && db_->is_open()) |
| db_->Raze(); |
| meta_table_.Reset(); |
| db_.reset(); |
| } |
| |
| void SQLitePersistentStoreBackendBase::Commit() { |
| DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); |
| |
| { |
| base::AutoLock locked(before_commit_callback_lock_); |
| if (!before_commit_callback_.is_null()) |
| before_commit_callback_.Run(); |
| } |
| |
| DoCommit(); |
| } |
| |
| void SQLitePersistentStoreBackendBase::PostBackgroundTask( |
| const base::Location& origin, |
| base::OnceClosure task) { |
| if (!background_task_runner_->PostTask(origin, std::move(task))) { |
| LOG(WARNING) << "Failed to post task from " << origin.ToString() |
| << " to background_task_runner_."; |
| } |
| } |
| |
| void SQLitePersistentStoreBackendBase::PostClientTask( |
| const base::Location& origin, |
| base::OnceClosure task) { |
| if (!client_task_runner_->PostTask(origin, std::move(task))) { |
| LOG(WARNING) << "Failed to post task from " << origin.ToString() |
| << " to client_task_runner_."; |
| } |
| } |
| |
| bool SQLitePersistentStoreBackendBase::MigrateDatabaseSchema() { |
| // Version check. |
| if (!meta_table_.Init(db_.get(), current_version_number_, |
| compatible_version_number_)) { |
| return false; |
| } |
| |
| if (meta_table_.GetCompatibleVersionNumber() > current_version_number_) { |
| LOG(WARNING) << histogram_tag_ << " database is too new."; |
| return false; |
| } |
| |
| // |cur_version| is the version that the database ends up at, after all the |
| // database upgrade statements. |
| base::Optional<int> cur_version = DoMigrateDatabaseSchema(); |
| if (!cur_version.has_value()) |
| return false; |
| |
| if (cur_version.value() < current_version_number_) { |
| base::UmaHistogramCounts100(histogram_tag_ + ".CorruptMetaTable", 1); |
| |
| meta_table_.Reset(); |
| db_ = std::make_unique<sql::Database>(); |
| if (!sql::Database::Delete(path_) || !db()->Open(path_) || |
| !meta_table_.Init(db(), current_version_number_, |
| compatible_version_number_)) { |
| base::UmaHistogramCounts100( |
| histogram_tag_ + ".CorruptMetaTableRecoveryFailed", 1); |
| NOTREACHED() << "Unable to reset the " << histogram_tag_ << " DB."; |
| meta_table_.Reset(); |
| db_.reset(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void SQLitePersistentStoreBackendBase::FlushAndNotifyInBackground( |
| base::OnceClosure callback) { |
| DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); |
| |
| Commit(); |
| if (callback) |
| PostClientTask(FROM_HERE, std::move(callback)); |
| } |
| |
| void SQLitePersistentStoreBackendBase::DoCloseInBackground() { |
| DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); |
| // Commit any pending operations |
| Commit(); |
| |
| meta_table_.Reset(); |
| db_.reset(); |
| } |
| |
| void SQLitePersistentStoreBackendBase::DatabaseErrorCallback( |
| int error, |
| sql::Statement* stmt) { |
| DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (!sql::IsErrorCatastrophic(error)) |
| return; |
| |
| // TODO(shess): Running KillDatabase() multiple times should be |
| // safe. |
| if (corruption_detected_) |
| return; |
| |
| corruption_detected_ = true; |
| |
| // Don't just do the close/delete here, as we are being called by |db| and |
| // that seems dangerous. |
| // TODO(shess): Consider just calling RazeAndClose() immediately. |
| // db_ may not be safe to reset at this point, but RazeAndClose() |
| // would cause the stack to unwind safely with errors. |
| PostBackgroundTask( |
| FROM_HERE, |
| base::BindOnce(&SQLitePersistentStoreBackendBase::KillDatabase, this)); |
| } |
| |
| void SQLitePersistentStoreBackendBase::KillDatabase() { |
| DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (db_) { |
| // This Backend will now be in-memory only. In a future run we will recreate |
| // the database. Hopefully things go better then! |
| bool success = db_->RazeAndClose(); |
| base::UmaHistogramBoolean(histogram_tag_ + ".KillDatabaseResult", success); |
| meta_table_.Reset(); |
| db_.reset(); |
| } |
| } |
| |
| } // namespace net |