Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(428)

Side by Side Diff: sql/recovery.cc

Issue 19281002: [sql] Scoped recovery framework. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase to match Open() retry logic. Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sql/recovery.h ('k') | sql/recovery_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013 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 "sql/recovery.h"
6
7 #include "base/files/file_path.h"
8 #include "base/logging.h"
9 #include "base/metrics/sparse_histogram.h"
10 #include "sql/connection.h"
11 #include "third_party/sqlite/sqlite3.h"
12
13 namespace sql {
14
15 // static
16 scoped_ptr<Recovery> Recovery::Begin(
17 Connection* connection,
18 const base::FilePath& db_path) {
19 scoped_ptr<Recovery> r(new Recovery(connection));
20 if (!r->Init(db_path)) {
21 // TODO(shess): Should Init() failure result in Raze()?
22 r->Shutdown(POISON);
23 return scoped_ptr<Recovery>();
24 }
25
26 return r.Pass();
27 }
28
29 // static
30 bool Recovery::Recovered(scoped_ptr<Recovery> r) {
31 return r->Backup();
32 }
33
34 // static
35 void Recovery::Unrecoverable(scoped_ptr<Recovery> r) {
36 CHECK(r->db_);
37 // ~Recovery() will RAZE_AND_POISON.
38 }
39
40 Recovery::Recovery(Connection* connection)
41 : db_(connection),
42 recover_db_() {
43 // Result should keep the page size specified earlier.
44 if (db_->page_size_)
45 recover_db_.set_page_size(db_->page_size_);
46
47 // TODO(shess): This may not handle cases where the default page
48 // size is used, but the default has changed. I do not think this
49 // has ever happened. This could be handled by using "PRAGMA
50 // page_size", at the cost of potential additional failure cases.
51 }
52
53 Recovery::~Recovery() {
54 Shutdown(RAZE_AND_POISON);
55 }
56
57 bool Recovery::Init(const base::FilePath& db_path) {
58 // Prevent the possibility of re-entering this code due to errors
59 // which happen while executing this code.
60 DCHECK(!db_->has_error_callback());
61
62 // Break any outstanding transactions on the original database to
63 // prevent deadlocks reading through the attached version.
64 // TODO(shess): A client may legitimately wish to recover from
65 // within the transaction context, because it would potentially
66 // preserve any in-flight changes. Unfortunately, any attach-based
67 // system could not handle that. A system which manually queried
68 // one database and stored to the other possibly could, but would be
69 // more complicated.
70 db_->RollbackAllTransactions();
71
72 if (!recover_db_.OpenTemporary())
73 return false;
74
75 // Turn on |SQLITE_RecoveryMode| for the handle, which allows
76 // reading certain broken databases.
77 if (!recover_db_.Execute("PRAGMA writable_schema=1"))
78 return false;
79
80 if (!recover_db_.AttachDatabase(db_path, "corrupt"))
81 return false;
82
83 return true;
84 }
85
86 bool Recovery::Backup() {
87 CHECK(db_);
88 CHECK(recover_db_.is_open());
89
90 // TODO(shess): Some of the failure cases here may need further
91 // exploration. Just as elsewhere, persistent problems probably
92 // need to be razed, while anything which might succeed on a future
93 // run probably should be allowed to try. But since Raze() uses the
94 // same approach, even that wouldn't work when this code fails.
95 //
96 // The documentation for the backup system indicate a relatively
97 // small number of errors are expected:
98 // SQLITE_BUSY - cannot lock the destination database. This should
99 // only happen if someone has another handle to the
100 // database, Chromium generally doesn't do that.
101 // SQLITE_LOCKED - someone locked the source database. Should be
102 // impossible (perhaps anti-virus could?).
103 // SQLITE_READONLY - destination is read-only.
104 // SQLITE_IOERR - since source database is temporary, probably
105 // indicates that the destination contains blocks
106 // throwing errors, or gross filesystem errors.
107 // SQLITE_NOMEM - out of memory, should be transient.
108 //
109 // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered
110 // transient, with SQLITE_LOCKED being unclear.
111 //
112 // SQLITE_READONLY and SQLITE_IOERR are probably persistent, with a
113 // strong chance that Raze() would not resolve them. If Delete()
114 // deletes the database file, the code could then re-open the file
115 // and attempt the backup again.
116 //
117 // For now, this code attempts a best effort and records histograms
118 // to inform future development.
119
120 // Backup the original db from the recovered db.
121 const char* kMain = "main";
122 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain,
123 recover_db_.db_, kMain);
124 if (!backup) {
125 // Error code is in the destination database handle.
126 int err = sqlite3_errcode(db_->db_);
127 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err);
128 LOG(ERROR) << "sqlite3_backup_init() failed: "
129 << sqlite3_errmsg(db_->db_);
130 return false;
131 }
132
133 // -1 backs up the entire database.
134 int rc = sqlite3_backup_step(backup, -1);
135 int pages = sqlite3_backup_pagecount(backup);
136 // TODO(shess): sqlite3_backup_finish() appears to allow returning a
137 // different value from sqlite3_backup_step(). Circle back and
138 // figure out if that can usefully inform the decision of whether to
139 // retry or not.
140 sqlite3_backup_finish(backup);
141 DCHECK_GT(pages, 0);
142
143 if (rc != SQLITE_DONE) {
144 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc);
145 LOG(ERROR) << "sqlite3_backup_step() failed: "
146 << sqlite3_errmsg(db_->db_);
147 }
148
149 // The destination database was locked. Give up, but leave the data
150 // in place. Maybe it won't be locked next time.
151 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
152 Shutdown(POISON);
153 return false;
154 }
155
156 // Running out of memory should be transient, retry later.
157 if (rc == SQLITE_NOMEM) {
158 Shutdown(POISON);
159 return false;
160 }
161
162 // TODO(shess): For now, leave the original database alone, pending
163 // results from Sqlite.RecoveryStep. Some errors should probably
164 // route to RAZE_AND_POISON.
165 if (rc != SQLITE_DONE) {
166 Shutdown(POISON);
167 return false;
168 }
169
170 // Clean up the recovery db, and terminate the main database
171 // connection.
172 Shutdown(POISON);
173 return true;
174 }
175
176 void Recovery::Shutdown(Recovery::Disposition raze) {
177 if (!db_)
178 return;
179
180 recover_db_.Close();
181 if (raze == RAZE_AND_POISON) {
182 db_->RazeAndClose();
183 } else if (raze == POISON) {
184 db_->Poison();
185 }
186 db_ = NULL;
187 }
188
189 } // namespace sql
OLDNEW
« no previous file with comments | « sql/recovery.h ('k') | sql/recovery_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698