Index: components/history/core/browser/thumbnail_database.cc |
diff --git a/components/history/core/browser/thumbnail_database.cc b/components/history/core/browser/thumbnail_database.cc |
index f476c6e0db1ab3b2115a033b0b7e7452fecdcdde..d9bc22defb1f9eefac4d2f0c6154dd757ffc920a 100644 |
--- a/components/history/core/browser/thumbnail_database.cc |
+++ b/components/history/core/browser/thumbnail_database.cc |
@@ -82,9 +82,9 @@ namespace { |
// fatal (in fact, very old data may be expired immediately at startup |
// anyhow). |
-// Version 8: ???????? by rogerm@chromium.org on 2015-??-?? |
+// Version 8: 982ef2c1/r323176 by rogerm@chromium.org on 2015-03-31 |
// Version 7: 911a634d/r209424 by qsr@chromium.org on 2013-07-01 |
-// Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20 |
+// Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20 (depr.) |
// Version 5: e2ee8ae9/r105004 by groby@chromium.org on 2011-10-12 (deprecated) |
// Version 4: 5f104d76/r77288 by sky@chromium.org on 2011-03-08 (deprecated) |
// Version 3: 09911bf3/r15 by initial.commit on 2008-07-26 (deprecated) |
@@ -94,7 +94,7 @@ namespace { |
// the new version and a test to verify that Init() works with it. |
const int kCurrentVersionNumber = 8; |
const int kCompatibleVersionNumber = 8; |
-const int kDeprecatedVersionNumber = 5; // and earlier. |
+const int kDeprecatedVersionNumber = 6; // and earlier. |
void FillIconMapping(const sql::Statement& statement, |
const GURL& page_url, |
@@ -146,8 +146,7 @@ void GenerateDiagnostics(sql::Connection* db, |
} |
// NOTE(shess): Schema modifications must consider initial creation in |
-// |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in |
-// |RetainDataForPageUrls()|. |
+// |InitImpl()| and history pruning in |RetainDataForPageUrls()|. |
bool InitTables(sql::Connection* db) { |
const char kIconMappingSql[] = |
"CREATE TABLE IF NOT EXISTS icon_mapping" |
@@ -190,8 +189,7 @@ bool InitTables(sql::Connection* db) { |
} |
// NOTE(shess): Schema modifications must consider initial creation in |
-// |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in |
-// |RetainDataForPageUrls()|. |
+// |InitImpl()| and history pruning in |RetainDataForPageUrls()|. |
bool InitIndices(sql::Connection* db) { |
const char kIconMappingUrlIndexSql[] = |
"CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" |
@@ -218,199 +216,6 @@ bool InitIndices(sql::Connection* db) { |
return true; |
} |
-enum RecoveryEventType { |
- RECOVERY_EVENT_RECOVERED = 0, |
- RECOVERY_EVENT_FAILED_SCOPER, |
- RECOVERY_EVENT_FAILED_META_VERSION_ERROR, // obsolete |
- RECOVERY_EVENT_FAILED_META_VERSION_NONE, // obsolete |
- RECOVERY_EVENT_FAILED_META_WRONG_VERSION6, // obsolete |
- RECOVERY_EVENT_FAILED_META_WRONG_VERSION5, // obsolete |
- RECOVERY_EVENT_FAILED_META_WRONG_VERSION, |
- RECOVERY_EVENT_FAILED_RECOVER_META, // obsolete |
- RECOVERY_EVENT_FAILED_META_INSERT, // obsolete |
- RECOVERY_EVENT_FAILED_INIT, |
- RECOVERY_EVENT_FAILED_RECOVER_FAVICONS, // obsolete |
- RECOVERY_EVENT_FAILED_FAVICONS_INSERT, // obsolete |
- RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS, // obsolete |
- RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT, // obsolete |
- RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING, // obsolete |
- RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT, // obsolete |
- RECOVERY_EVENT_RECOVERED_VERSION6, // obsolete |
- RECOVERY_EVENT_FAILED_META_INIT, |
- RECOVERY_EVENT_FAILED_META_VERSION, |
- RECOVERY_EVENT_DEPRECATED, |
- RECOVERY_EVENT_FAILED_V5_INITSCHEMA, // obsolete |
- RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS, // obsolete |
- RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING, // obsolete |
- RECOVERY_EVENT_RECOVERED_VERSION5, // obsolete |
- RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS, |
- RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS, |
- RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING, |
- RECOVERY_EVENT_FAILED_COMMIT, |
- |
- // Always keep this at the end. |
- RECOVERY_EVENT_MAX, |
-}; |
- |
-void RecordRecoveryEvent(RecoveryEventType recovery_event) { |
- UMA_HISTOGRAM_ENUMERATION("History.FaviconsRecovery", |
- recovery_event, RECOVERY_EVENT_MAX); |
-} |
- |
-// Recover the database to the extent possible, razing it if recovery |
-// is not possible. |
-// TODO(shess): This is mostly just a safe proof of concept. In the |
-// real world, this database is probably not worthwhile recovering, as |
-// opposed to just razing it and starting over whenever corruption is |
-// detected. So this database is a good test subject. |
-void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { |
- // NOTE(shess): This code is currently specific to the version |
- // number. I am working on simplifying things to loosen the |
- // dependency, meanwhile contact me if you need to bump the version. |
- DCHECK_EQ(8, kCurrentVersionNumber); |
- |
- // TODO(shess): Reset back after? |
- db->reset_error_callback(); |
- |
- // For histogram purposes. |
- size_t favicons_rows_recovered = 0; |
- size_t favicon_bitmaps_rows_recovered = 0; |
- size_t icon_mapping_rows_recovered = 0; |
- int64_t original_size = 0; |
- base::GetFileSize(db_path, &original_size); |
- |
- std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); |
- if (!recovery) { |
- // TODO(shess): Unable to create recovery connection. This |
- // implies something substantial is wrong. At this point |db| has |
- // been poisoned so there is nothing really to do. |
- // |
- // Possible responses are unclear. If the failure relates to a |
- // problem somehow specific to the temporary file used to back the |
- // database, then an in-memory database could possibly be used. |
- // This could potentially allow recovering the main database, and |
- // might be simple to implement w/in Begin(). |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER); |
- return; |
- } |
- |
- // Setup the meta recovery table and fetch the version number from |
- // the corrupt database. |
- int version = 0; |
- if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) { |
- // TODO(shess): Prior histograms indicate all failures are in |
- // creating the recover virtual table for corrupt.meta. The table |
- // may not exist, or the database may be too far gone. Either |
- // way, unclear how to resolve. |
- sql::Recovery::Rollback(std::move(recovery)); |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION); |
- return; |
- } |
- |
- // This code may be able to fetch version information that the regular |
- // deprecation path cannot. |
- // NOTE(shess,rogerm): v6 is not currently deprecated in the normal Init() |
- // path, but is deprecated in the recovery path in the interest of keeping |
- // the code simple. http://crbug.com/327485 for numbers. |
- DCHECK_LE(kDeprecatedVersionNumber, 6); |
- if (version <= 6) { |
- sql::Recovery::Unrecoverable(std::move(recovery)); |
- RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED); |
- return; |
- } |
- |
- // Earlier versions have been handled or deprecated. |
- if (version < 7) { |
- sql::Recovery::Unrecoverable(std::move(recovery)); |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION); |
- return; |
- } |
- |
- // Recover to current schema version. |
- sql::MetaTable recover_meta_table; |
- if (!recover_meta_table.Init(recovery->db(), kCurrentVersionNumber, |
- kCompatibleVersionNumber)) { |
- sql::Recovery::Rollback(std::move(recovery)); |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); |
- return; |
- } |
- |
- // Create a fresh version of the database. The recovery code uses |
- // conflict-resolution to handle duplicates, so the indices are |
- // necessary. |
- if (!InitTables(recovery->db()) || !InitIndices(recovery->db())) { |
- // TODO(shess): Unable to create the new schema in the new |
- // database. The new database should be a temporary file, so |
- // being unable to work with it is pretty unclear. |
- // |
- // What are the potential responses, even? The recovery database |
- // could be opened as in-memory. If the temp database had a |
- // filesystem problem and the temp filesystem differs from the |
- // main database, then that could fix it. |
- sql::Recovery::Rollback(std::move(recovery)); |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_INIT); |
- return; |
- } |
- |
- if (!recovery->AutoRecoverTable("favicons", &favicons_rows_recovered)) { |
- sql::Recovery::Rollback(std::move(recovery)); |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS); |
- return; |
- } |
- if (!recovery->AutoRecoverTable("favicon_bitmaps", |
- &favicon_bitmaps_rows_recovered)) { |
- sql::Recovery::Rollback(std::move(recovery)); |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS); |
- return; |
- } |
- if (!recovery->AutoRecoverTable("icon_mapping", |
- &icon_mapping_rows_recovered)) { |
- sql::Recovery::Rollback(std::move(recovery)); |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING); |
- return; |
- } |
- |
- // TODO(shess): Is it possible/likely to have broken foreign-key |
- // issues with the tables? |
- // - icon_mapping.icon_id maps to no favicons.id |
- // - favicon_bitmaps.icon_id maps to no favicons.id |
- // - favicons.id is referenced by no icon_mapping.icon_id |
- // - favicons.id is referenced by no favicon_bitmaps.icon_id |
- // This step is possibly not worth the effort necessary to develop |
- // and sequence the statements, as it is basically a form of garbage |
- // collection. |
- |
- if (!sql::Recovery::Recovered(std::move(recovery))) { |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT); |
- return; |
- } |
- |
- // Track the size of the recovered database relative to the size of |
- // the input database. The size should almost always be smaller, |
- // unless the input database was empty to start with. If the |
- // percentage results are very low, something is awry. |
- int64_t final_size = 0; |
- if (original_size > 0 && |
- base::GetFileSize(db_path, &final_size) && |
- final_size > 0) { |
- int percentage = static_cast<int>(original_size * 100 / final_size); |
- UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage", |
- std::max(100, percentage)); |
- } |
- |
- // Using 10,000 because these cases mostly care about "none |
- // recovered" and "lots recovered". More than 10,000 rows recovered |
- // probably means there's something wrong with the profile. |
- UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons", |
- static_cast<int>(favicons_rows_recovered)); |
- UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps", |
- static_cast<int>(favicon_bitmaps_rows_recovered)); |
- UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping", |
- static_cast<int>(icon_mapping_rows_recovered)); |
- |
- RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED); |
-} |
- |
void DatabaseErrorCallback(sql::Connection* db, |
const base::FilePath& db_path, |
HistoryBackendClient* backend_client, |
@@ -425,11 +230,36 @@ void DatabaseErrorCallback(sql::Connection* db, |
} |
// Attempt to recover corrupt databases. |
- int error = (extended_error & 0xFF); |
- if (error == SQLITE_CORRUPT || |
- error == SQLITE_CANTOPEN || |
- error == SQLITE_NOTADB) { |
- RecoverDatabaseOrRaze(db, db_path); |
+ if (sql::Recovery::ShouldRecover(extended_error)) { |
+ // NOTE(shess): This approach is valid as of version 8. When bumping the |
+ // version, it will PROBABLY remain valid, but consider whether any schema |
+ // changes might break automated recovery. |
+ DCHECK_EQ(8, kCurrentVersionNumber); |
+ |
+ // Prevent reentrant calls. |
+ db->reset_error_callback(); |
+ |
+ // TODO(shess): Is it possible/likely to have broken foreign-key |
+ // issues with the tables? |
+ // - icon_mapping.icon_id maps to no favicons.id |
+ // - favicon_bitmaps.icon_id maps to no favicons.id |
+ // - favicons.id is referenced by no icon_mapping.icon_id |
+ // - favicons.id is referenced by no favicon_bitmaps.icon_id |
+ // This step is possibly not worth the effort necessary to develop |
+ // and sequence the statements, as it is basically a form of garbage |
+ // collection. |
+ |
+ // After this call, the |db| handle is poisoned so that future calls will |
+ // return errors until the handle is re-opened. |
+ sql::Recovery::RecoverDatabaseWithMetaVersion(db, db_path); |
+ |
+ // The DLOG(FATAL) below is intended to draw immediate attention to errors |
+ // in newly-written code. Database corruption is generally a result of OS |
+ // or hardware issues, not coding errors at the client level, so displaying |
+ // the error would probably lead to confusion. The ignored call signals the |
+ // test-expectation framework that the error was handled. |
+ ignore_result(sql::Connection::IsExpectedSqliteError(extended_error)); |
+ return; |
} |
// The default handling is to assert on debug and to ignore on release. |