Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/net/sqlite_persistent_cookie_store.h" | 5 #include "chrome/browser/net/sqlite_persistent_cookie_store.h" |
| 6 | 6 |
| 7 #include <list> | 7 #include <list> |
| 8 #include <map> | 8 #include <map> |
| 9 #include <set> | 9 #include <set> |
| 10 #include <utility> | 10 #include <utility> |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 25 #include "base/time.h" | 25 #include "base/time.h" |
| 26 #include "chrome/browser/diagnostics/sqlite_diagnostics.h" | 26 #include "chrome/browser/diagnostics/sqlite_diagnostics.h" |
| 27 #include "chrome/browser/net/clear_on_exit_policy.h" | 27 #include "chrome/browser/net/clear_on_exit_policy.h" |
| 28 #include "content/public/browser/browser_thread.h" | 28 #include "content/public/browser/browser_thread.h" |
| 29 #include "googleurl/src/gurl.h" | 29 #include "googleurl/src/gurl.h" |
| 30 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | 30 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| 31 #include "net/cookies/canonical_cookie.h" | 31 #include "net/cookies/canonical_cookie.h" |
| 32 #include "sql/meta_table.h" | 32 #include "sql/meta_table.h" |
| 33 #include "sql/statement.h" | 33 #include "sql/statement.h" |
| 34 #include "sql/transaction.h" | 34 #include "sql/transaction.h" |
| 35 #include "third_party/sqlite/sqlite3.h" | |
| 35 | 36 |
| 36 using base::Time; | 37 using base::Time; |
| 37 using content::BrowserThread; | 38 using content::BrowserThread; |
| 38 | 39 |
| 39 // This class is designed to be shared between any calling threads and the | 40 // This class is designed to be shared between any calling threads and the |
| 40 // database thread. It batches operations and commits them on a timer. | 41 // database thread. It batches operations and commits them on a timer. |
| 41 // | 42 // |
| 42 // SQLitePersistentCookieStore::Load is called to load all cookies. It | 43 // SQLitePersistentCookieStore::Load is called to load all cookies. It |
| 43 // delegates to Backend::Load, which posts a Backend::LoadAndNotifyOnDBThread | 44 // delegates to Backend::Load, which posts a Backend::LoadAndNotifyOnDBThread |
| 44 // task to the DB thread. This task calls Backend::ChainLoadCookies(), which | 45 // task to the DB thread. This task calls Backend::ChainLoadCookies(), which |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 67 : path_(path), | 68 : path_(path), |
| 68 db_(NULL), | 69 db_(NULL), |
| 69 num_pending_(0), | 70 num_pending_(0), |
| 70 force_keep_session_state_(false), | 71 force_keep_session_state_(false), |
| 71 initialized_(false), | 72 initialized_(false), |
| 72 restore_old_session_cookies_(restore_old_session_cookies), | 73 restore_old_session_cookies_(restore_old_session_cookies), |
| 73 clear_on_exit_policy_(clear_on_exit_policy), | 74 clear_on_exit_policy_(clear_on_exit_policy), |
| 74 num_cookies_read_(0), | 75 num_cookies_read_(0), |
| 75 num_priority_waiting_(0), | 76 num_priority_waiting_(0), |
| 76 total_priority_requests_(0) { | 77 total_priority_requests_(0) { |
| 78 error_delegate_ = | |
| 79 new KillDatabaseErrorDelegate(this, GetErrorHandlerForCookieDb()); | |
| 77 } | 80 } |
| 78 | 81 |
| 79 // Creates or loads the SQLite database. | 82 // Creates or loads the SQLite database. |
| 80 void Load(const LoadedCallback& loaded_callback); | 83 void Load(const LoadedCallback& loaded_callback); |
| 81 | 84 |
| 82 // Loads cookies for the domain key (eTLD+1). | 85 // Loads cookies for the domain key (eTLD+1). |
| 83 void LoadCookiesForKey(const std::string& domain, | 86 void LoadCookiesForKey(const std::string& domain, |
| 84 const LoadedCallback& loaded_callback); | 87 const LoadedCallback& loaded_callback); |
| 85 | 88 |
| 86 // Batch a cookie addition. | 89 // Batch a cookie addition. |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 97 | 100 |
| 98 // Commit any pending operations and close the database. This must be called | 101 // Commit any pending operations and close the database. This must be called |
| 99 // before the object is destructed. | 102 // before the object is destructed. |
| 100 void Close(); | 103 void Close(); |
| 101 | 104 |
| 102 void SetForceKeepSessionState(); | 105 void SetForceKeepSessionState(); |
| 103 | 106 |
| 104 private: | 107 private: |
| 105 friend class base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend>; | 108 friend class base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend>; |
| 106 | 109 |
| 110 class KillDatabaseErrorDelegate : public sql::ErrorDelegate { | |
| 111 public: | |
| 112 KillDatabaseErrorDelegate(Backend* backend, | |
| 113 sql::ErrorDelegate* wrapped_delegate); | |
| 114 // ErrorDelegate implementation. | |
| 115 virtual int OnError(int error, | |
| 116 sql::Connection* connection, | |
| 117 sql::Statement* stmt) OVERRIDE; | |
| 118 | |
| 119 void reset_backend() { | |
| 120 backend_ = NULL; | |
| 121 } | |
| 122 | |
| 123 private: | |
| 124 // Do not increment the count on Backend, as that would create a circular | |
| 125 // reference (Backend -> Connection -> ErrorDelegate -> Backend). Instead, | |
| 126 // Backend will call reset_backend() when it is going away. | |
|
Scott Hess - ex-Googler
2012/09/14 17:15:16
Rather than doing this dance, would it make sense
erikwright (departed)
2012/09/14 18:11:02
Then you would have the circular reference of Back
Scott Hess - ex-Googler
2012/09/14 18:34:03
Doh!
erikwright (departed)
2012/09/14 18:43:26
We hold a pointer to the KillDatabaseErrorDelegate
| |
| 127 Backend* backend_; | |
| 128 scoped_refptr<sql::ErrorDelegate> wrapped_delegate_; | |
| 129 | |
| 130 DISALLOW_COPY_AND_ASSIGN(KillDatabaseErrorDelegate); | |
| 131 }; | |
| 132 | |
| 107 // You should call Close() before destructing this object. | 133 // You should call Close() before destructing this object. |
| 108 ~Backend() { | 134 ~Backend() { |
| 109 DCHECK(!db_.get()) << "Close should have already been called."; | 135 DCHECK(!db_.get()) << "Close should have already been called."; |
| 110 DCHECK(num_pending_ == 0 && pending_.empty()); | 136 DCHECK(num_pending_ == 0 && pending_.empty()); |
| 111 } | 137 } |
| 112 | 138 |
| 113 // Database upgrade statements. | 139 // Database upgrade statements. |
| 114 bool EnsureDatabaseVersion(); | 140 bool EnsureDatabaseVersion(); |
| 115 | 141 |
| 116 class PendingOperation { | 142 class PendingOperation { |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 182 const net::CanonicalCookie& cc); | 208 const net::CanonicalCookie& cc); |
| 183 // Commit our pending operations to the database. | 209 // Commit our pending operations to the database. |
| 184 void Commit(); | 210 void Commit(); |
| 185 // Close() executed on the background thread. | 211 // Close() executed on the background thread. |
| 186 void InternalBackgroundClose(); | 212 void InternalBackgroundClose(); |
| 187 | 213 |
| 188 void DeleteSessionCookiesOnStartup(); | 214 void DeleteSessionCookiesOnStartup(); |
| 189 | 215 |
| 190 void DeleteSessionCookiesOnShutdown(); | 216 void DeleteSessionCookiesOnShutdown(); |
| 191 | 217 |
| 218 void KillDatabase(); | |
| 219 | |
| 192 FilePath path_; | 220 FilePath path_; |
| 193 scoped_ptr<sql::Connection> db_; | 221 scoped_ptr<sql::Connection> db_; |
| 222 scoped_refptr<KillDatabaseErrorDelegate> error_delegate_; | |
| 194 sql::MetaTable meta_table_; | 223 sql::MetaTable meta_table_; |
| 195 | 224 |
| 196 typedef std::list<PendingOperation*> PendingOperationsList; | 225 typedef std::list<PendingOperation*> PendingOperationsList; |
| 197 PendingOperationsList pending_; | 226 PendingOperationsList pending_; |
| 198 PendingOperationsList::size_type num_pending_; | 227 PendingOperationsList::size_type num_pending_; |
| 199 // True if the persistent store should skip delete on exit rules. | 228 // True if the persistent store should skip delete on exit rules. |
| 200 bool force_keep_session_state_; | 229 bool force_keep_session_state_; |
| 201 // Guard |cookies_|, |pending_|, |num_pending_|, |force_keep_session_state_| | 230 // Guard |cookies_|, |pending_|, |num_pending_|, |force_keep_session_state_| |
| 202 base::Lock lock_; | 231 base::Lock lock_; |
| 203 | 232 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 240 int total_priority_requests_; | 269 int total_priority_requests_; |
| 241 // The time when |num_priority_waiting_| incremented to 1. | 270 // The time when |num_priority_waiting_| incremented to 1. |
| 242 base::Time current_priority_wait_start_; | 271 base::Time current_priority_wait_start_; |
| 243 // The cumulative duration of time when |num_priority_waiting_| was greater | 272 // The cumulative duration of time when |num_priority_waiting_| was greater |
| 244 // than 1. | 273 // than 1. |
| 245 base::TimeDelta priority_wait_duration_; | 274 base::TimeDelta priority_wait_duration_; |
| 246 | 275 |
| 247 DISALLOW_COPY_AND_ASSIGN(Backend); | 276 DISALLOW_COPY_AND_ASSIGN(Backend); |
| 248 }; | 277 }; |
| 249 | 278 |
| 279 SQLitePersistentCookieStore::Backend::KillDatabaseErrorDelegate:: | |
| 280 KillDatabaseErrorDelegate(Backend* backend, | |
| 281 sql::ErrorDelegate* wrapped_delegate) | |
| 282 : backend_(backend), | |
| 283 wrapped_delegate_(wrapped_delegate) { | |
| 284 } | |
| 285 | |
| 286 int SQLitePersistentCookieStore::Backend::KillDatabaseErrorDelegate::OnError( | |
| 287 int error, sql::Connection* connection, sql::Statement* stmt) { | |
| 288 if (wrapped_delegate_.get()) | |
| 289 error = wrapped_delegate_->OnError(error, connection, stmt); | |
| 290 | |
| 291 bool delete_db = false; | |
| 292 | |
| 293 switch (error) { | |
| 294 case SQLITE_DONE: | |
| 295 case SQLITE_OK: | |
| 296 // Theoretically, the wrapped delegate might have resolved the error, and | |
| 297 // we would end up here. | |
| 298 break; | |
| 299 | |
| 300 case SQLITE_CORRUPT: | |
| 301 case SQLITE_NOTADB: | |
| 302 // Highly unlikely we would ever recover from these. | |
| 303 delete_db = true; | |
| 304 break; | |
| 305 | |
| 306 | |
| 307 case SQLITE_ERROR: | |
|
Scott Hess - ex-Googler
2012/09/14 17:01:34
This an SQLITE_CONSTRAINT are _probably_ cases whe
| |
| 308 // TODO(erikwright): Figure out whether this is likely to be recoverable. | |
| 309 break; | |
| 310 | |
| 311 case SQLITE_CANTOPEN: | |
| 312 // TODO(erikwright): Figure out what this means. | |
| 313 break; | |
|
Scott Hess - ex-Googler
2012/09/14 17:01:34
Dammit, I wish I remember where I was seeing this,
| |
| 314 | |
| 315 case SQLITE_IOERR: | |
| 316 case SQLITE_BUSY: | |
| 317 // Presumably transient. | |
| 318 break; | |
|
Scott Hess - ex-Googler
2012/09/14 17:01:34
It is possible to write the code such that SQLITE_
erikwright (departed)
2012/09/14 18:11:02
SQLITE_IOERR is a relatively commonly occurring er
| |
| 319 | |
| 320 case SQLITE_TOOBIG: | |
| 321 case SQLITE_FULL: | |
| 322 case SQLITE_NOMEM: | |
| 323 // Not a problem with the database. | |
| 324 break; | |
| 325 | |
| 326 case SQLITE_READONLY: | |
| 327 // Presumably either transient or we don't have the privileges to | |
| 328 // move/delete the file anyway. | |
| 329 break; | |
|
Scott Hess - ex-Googler
2012/09/14 17:01:34
We might be able to delete a read-only file. Depe
erikwright (departed)
2012/09/14 18:11:02
It is a commonly occuring error. Again, I'll dig i
| |
| 330 | |
| 331 case SQLITE_CONSTRAINT: | |
| 332 // Presumably there is a schema upgrade failure. | |
| 333 // TODO(erikwright): Shall we kill the DB as an easy way out? | |
| 334 break; | |
| 335 | |
| 336 case SQLITE_LOCKED: | |
| 337 case SQLITE_INTERNAL: | |
| 338 case SQLITE_PERM: | |
| 339 case SQLITE_ABORT: | |
| 340 case SQLITE_INTERRUPT: | |
| 341 case SQLITE_NOTFOUND: | |
| 342 case SQLITE_PROTOCOL: | |
| 343 case SQLITE_EMPTY: | |
| 344 case SQLITE_SCHEMA: | |
| 345 case SQLITE_MISMATCH: | |
| 346 case SQLITE_MISUSE: | |
| 347 case SQLITE_NOLFS: | |
| 348 case SQLITE_AUTH: | |
| 349 case SQLITE_FORMAT: | |
| 350 case SQLITE_RANGE: | |
| 351 case SQLITE_ROW: | |
| 352 // None of these appear in error reports, so for now let's not try to | |
| 353 // guess at how to handle them. | |
| 354 break; | |
| 355 } | |
| 356 | |
| 357 if (delete_db && backend_) { | |
| 358 // Don't just do the close/delete here, as we are being called by |db| and | |
| 359 // that seems dangerous. | |
| 360 MessageLoop::current()->PostTask( | |
| 361 FROM_HERE, base::Bind(&Backend::KillDatabase, backend_)); | |
| 362 | |
| 363 // Avoid being called more than once. There should still be a reference to | |
| 364 // this ErrorDelegate in the backend, but just in case don't refer to any | |
| 365 // members from here forward. | |
| 366 connection->set_error_delegate(wrapped_delegate_.get()); | |
| 367 } | |
| 368 | |
| 369 return error; | |
| 370 } | |
| 371 | |
| 250 // Version number of the database. | 372 // Version number of the database. |
| 251 // | 373 // |
| 252 // Version 5 adds the columns has_expires and is_persistent, so that the | 374 // Version 5 adds the columns has_expires and is_persistent, so that the |
| 253 // database can store session cookies as well as persistent cookies. Databases | 375 // database can store session cookies as well as persistent cookies. Databases |
| 254 // of version 5 are incompatible with older versions of code. If a database of | 376 // of version 5 are incompatible with older versions of code. If a database of |
| 255 // version 5 is read by older code, session cookies will be treated as normal | 377 // version 5 is read by older code, session cookies will be treated as normal |
| 256 // cookies. Currently, these fields are written, but not read anymore. | 378 // cookies. Currently, these fields are written, but not read anymore. |
| 257 // | 379 // |
| 258 // In version 4, we migrated the time epoch. If you open the DB with an older | 380 // In version 4, we migrated the time epoch. If you open the DB with an older |
| 259 // version on Mac or Linux, the times will look wonky, but the file will likely | 381 // version on Mac or Linux, the times will look wonky, but the file will likely |
| (...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 487 return false; | 609 return false; |
| 488 } | 610 } |
| 489 | 611 |
| 490 int64 db_size = 0; | 612 int64 db_size = 0; |
| 491 if (file_util::GetFileSize(path_, &db_size)) { | 613 if (file_util::GetFileSize(path_, &db_size)) { |
| 492 base::ThreadRestrictions::ScopedAllowIO allow_io; | 614 base::ThreadRestrictions::ScopedAllowIO allow_io; |
| 493 UMA_HISTOGRAM_COUNTS("Cookie.DBSizeInKB", db_size / 1024 ); | 615 UMA_HISTOGRAM_COUNTS("Cookie.DBSizeInKB", db_size / 1024 ); |
| 494 } | 616 } |
| 495 | 617 |
| 496 db_.reset(new sql::Connection); | 618 db_.reset(new sql::Connection); |
| 619 db_->set_error_delegate(error_delegate_.get()); | |
| 620 | |
| 497 if (!db_->Open(path_)) { | 621 if (!db_->Open(path_)) { |
| 498 NOTREACHED() << "Unable to open cookie DB."; | 622 NOTREACHED() << "Unable to open cookie DB."; |
| 499 db_.reset(); | 623 db_.reset(); |
| 500 return false; | 624 return false; |
| 501 } | 625 } |
| 502 | 626 |
| 503 db_->set_error_delegate(GetErrorHandlerForCookieDb()); | |
| 504 | |
| 505 if (!EnsureDatabaseVersion() || !InitTable(db_.get())) { | 627 if (!EnsureDatabaseVersion() || !InitTable(db_.get())) { |
| 506 NOTREACHED() << "Unable to open cookie DB."; | 628 NOTREACHED() << "Unable to open cookie DB."; |
| 507 db_.reset(); | 629 db_.reset(); |
| 508 return false; | 630 return false; |
| 509 } | 631 } |
| 510 | 632 |
| 511 UMA_HISTOGRAM_CUSTOM_TIMES( | 633 UMA_HISTOGRAM_CUSTOM_TIMES( |
| 512 "Cookie.TimeInitializeDB", | 634 "Cookie.TimeInitializeDB", |
| 513 base::Time::Now() - start, | 635 base::Time::Now() - start, |
| 514 base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1), | 636 base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1), |
| (...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 918 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); | 1040 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); |
| 919 // Commit any pending operations | 1041 // Commit any pending operations |
| 920 Commit(); | 1042 Commit(); |
| 921 | 1043 |
| 922 if (!force_keep_session_state_ && clear_on_exit_policy_.get() && | 1044 if (!force_keep_session_state_ && clear_on_exit_policy_.get() && |
| 923 clear_on_exit_policy_->HasClearOnExitOrigins()) { | 1045 clear_on_exit_policy_->HasClearOnExitOrigins()) { |
| 924 DeleteSessionCookiesOnShutdown(); | 1046 DeleteSessionCookiesOnShutdown(); |
| 925 } | 1047 } |
| 926 | 1048 |
| 927 db_.reset(); | 1049 db_.reset(); |
| 1050 if (error_delegate_.get()) { | |
| 1051 error_delegate_->reset_backend(); | |
| 1052 error_delegate_ = NULL; | |
| 1053 } | |
| 928 } | 1054 } |
| 929 | 1055 |
| 930 void SQLitePersistentCookieStore::Backend::DeleteSessionCookiesOnShutdown() { | 1056 void SQLitePersistentCookieStore::Backend::DeleteSessionCookiesOnShutdown() { |
| 931 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); | 1057 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); |
| 932 | 1058 |
| 933 if (!db_.get()) | 1059 if (!db_.get()) |
| 934 return; | 1060 return; |
| 935 | 1061 |
| 936 sql::Statement del_smt(db_->GetCachedStatement( | 1062 sql::Statement del_smt(db_->GetCachedStatement( |
| 937 SQL_FROM_HERE, "DELETE FROM cookies WHERE host_key=? AND secure=?")); | 1063 SQL_FROM_HERE, "DELETE FROM cookies WHERE host_key=? AND secure=?")); |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 961 del_smt.BindString(0, it->first.first); | 1087 del_smt.BindString(0, it->first.first); |
| 962 del_smt.BindInt(1, it->first.second); | 1088 del_smt.BindInt(1, it->first.second); |
| 963 if (!del_smt.Run()) | 1089 if (!del_smt.Run()) |
| 964 NOTREACHED() << "Could not delete a cookie from the DB."; | 1090 NOTREACHED() << "Could not delete a cookie from the DB."; |
| 965 } | 1091 } |
| 966 | 1092 |
| 967 if (!transaction.Commit()) | 1093 if (!transaction.Commit()) |
| 968 LOG(WARNING) << "Unable to delete cookies on shutdown."; | 1094 LOG(WARNING) << "Unable to delete cookies on shutdown."; |
| 969 } | 1095 } |
| 970 | 1096 |
| 1097 void SQLitePersistentCookieStore::Backend::KillDatabase() { | |
| 1098 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); | |
| 1099 | |
| 1100 if (db_.get()) { | |
| 1101 // This Backend will now be in-memory only. In a future run we will recreate | |
| 1102 // the database. Hopefully things go better then! | |
| 1103 db_->Close(); | |
| 1104 db_.reset(); | |
| 1105 } | |
| 1106 | |
| 1107 bool success = file_util::Delete(path_, false); | |
|
Scott Hess - ex-Googler
2012/09/14 17:01:34
Suggest doing db_.Raze() before closing it. That
erikwright (departed)
2012/09/14 18:11:02
We can do the Raze for now and see what happens. W
| |
| 1108 UMA_HISTOGRAM_BOOLEAN("Cookie.KillDatabaseResult", success); | |
| 1109 } | |
| 1110 | |
| 971 void SQLitePersistentCookieStore::Backend::SetForceKeepSessionState() { | 1111 void SQLitePersistentCookieStore::Backend::SetForceKeepSessionState() { |
| 972 base::AutoLock locked(lock_); | 1112 base::AutoLock locked(lock_); |
| 973 force_keep_session_state_ = true; | 1113 force_keep_session_state_ = true; |
| 974 } | 1114 } |
| 975 | 1115 |
| 976 void SQLitePersistentCookieStore::Backend::DeleteSessionCookiesOnStartup() { | 1116 void SQLitePersistentCookieStore::Backend::DeleteSessionCookiesOnStartup() { |
| 977 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); | 1117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); |
| 978 if (!db_->Execute("DELETE FROM cookies WHERE persistent == 0")) | 1118 if (!db_->Execute("DELETE FROM cookies WHERE persistent == 0")) |
| 979 LOG(WARNING) << "Unable to delete session cookies."; | 1119 LOG(WARNING) << "Unable to delete session cookies."; |
| 980 } | 1120 } |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1026 } | 1166 } |
| 1027 | 1167 |
| 1028 SQLitePersistentCookieStore::~SQLitePersistentCookieStore() { | 1168 SQLitePersistentCookieStore::~SQLitePersistentCookieStore() { |
| 1029 if (backend_.get()) { | 1169 if (backend_.get()) { |
| 1030 backend_->Close(); | 1170 backend_->Close(); |
| 1031 // Release our reference, it will probably still have a reference if the | 1171 // Release our reference, it will probably still have a reference if the |
| 1032 // background thread has not run Close() yet. | 1172 // background thread has not run Close() yet. |
| 1033 backend_ = NULL; | 1173 backend_ = NULL; |
| 1034 } | 1174 } |
| 1035 } | 1175 } |
| OLD | NEW |