Home | History | Annotate | Download | only in indexed_db
      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 "content/browser/indexed_db/indexed_db_factory.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/logging.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "base/time/time.h"
     12 #include "content/browser/indexed_db/indexed_db_backing_store.h"
     13 #include "content/browser/indexed_db/indexed_db_context_impl.h"
     14 #include "content/browser/indexed_db/indexed_db_database_error.h"
     15 #include "content/browser/indexed_db/indexed_db_tracing.h"
     16 #include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
     17 #include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
     18 #include "third_party/leveldatabase/env_chromium.h"
     19 #include "webkit/common/database/database_identifier.h"
     20 
     21 using base::ASCIIToUTF16;
     22 
     23 namespace content {
     24 
     25 const int64 kBackingStoreGracePeriodMs = 2000;
     26 
     27 IndexedDBFactory::IndexedDBFactory(IndexedDBContextImpl* context)
     28     : context_(context) {}
     29 
     30 IndexedDBFactory::~IndexedDBFactory() {}
     31 
     32 void IndexedDBFactory::RemoveDatabaseFromMaps(
     33     const IndexedDBDatabase::Identifier& identifier) {
     34   IndexedDBDatabaseMap::iterator it = database_map_.find(identifier);
     35   DCHECK(it != database_map_.end());
     36   IndexedDBDatabase* database = it->second;
     37   database_map_.erase(it);
     38 
     39   std::pair<OriginDBMap::iterator, OriginDBMap::iterator> range =
     40       origin_dbs_.equal_range(database->identifier().first);
     41   DCHECK(range.first != range.second);
     42   for (OriginDBMap::iterator it2 = range.first; it2 != range.second; ++it2) {
     43     if (it2->second == database) {
     44       origin_dbs_.erase(it2);
     45       break;
     46     }
     47   }
     48 }
     49 
     50 void IndexedDBFactory::ReleaseDatabase(
     51     const IndexedDBDatabase::Identifier& identifier,
     52     bool forcedClose) {
     53 
     54   DCHECK(!database_map_.find(identifier)->second->backing_store());
     55 
     56   RemoveDatabaseFromMaps(identifier);
     57 
     58   // No grace period on a forced-close, as the initiator is
     59   // assuming the backing store will be released once all
     60   // connections are closed.
     61   ReleaseBackingStore(identifier.first, forcedClose);
     62 }
     63 
     64 void IndexedDBFactory::ReleaseBackingStore(const GURL& origin_url,
     65                                            bool immediate) {
     66   if (immediate) {
     67     IndexedDBBackingStoreMap::iterator it =
     68         backing_stores_with_active_blobs_.find(origin_url);
     69     if (it != backing_stores_with_active_blobs_.end()) {
     70       it->second->active_blob_registry()->ForceShutdown();
     71       backing_stores_with_active_blobs_.erase(it);
     72     }
     73   }
     74 
     75   // Only close if this is the last reference.
     76   if (!HasLastBackingStoreReference(origin_url))
     77     return;
     78 
     79   // If this factory does hold the last reference to the backing store, it can
     80   // be closed - but unless requested to close it immediately, keep it around
     81   // for a short period so that a re-open is fast.
     82   if (immediate) {
     83     CloseBackingStore(origin_url);
     84     return;
     85   }
     86 
     87   // Start a timer to close the backing store, unless something else opens it
     88   // in the mean time.
     89   DCHECK(!backing_store_map_[origin_url]->close_timer()->IsRunning());
     90   backing_store_map_[origin_url]->close_timer()->Start(
     91       FROM_HERE,
     92       base::TimeDelta::FromMilliseconds(kBackingStoreGracePeriodMs),
     93       base::Bind(&IndexedDBFactory::MaybeCloseBackingStore, this, origin_url));
     94 }
     95 
     96 void IndexedDBFactory::MaybeCloseBackingStore(const GURL& origin_url) {
     97   // Another reference may have opened since the maybe-close was posted, so it
     98   // is necessary to check again.
     99   if (HasLastBackingStoreReference(origin_url))
    100     CloseBackingStore(origin_url);
    101 }
    102 
    103 void IndexedDBFactory::CloseBackingStore(const GURL& origin_url) {
    104   IndexedDBBackingStoreMap::iterator it = backing_store_map_.find(origin_url);
    105   DCHECK(it != backing_store_map_.end());
    106   // Stop the timer (if it's running) - this may happen if the timer was started
    107   // and then a forced close occurs.
    108   it->second->close_timer()->Stop();
    109   backing_store_map_.erase(it);
    110 }
    111 
    112 bool IndexedDBFactory::HasLastBackingStoreReference(const GURL& origin_url)
    113     const {
    114   IndexedDBBackingStore* ptr;
    115   {
    116     // Scope so that the implicit scoped_refptr<> is freed.
    117     IndexedDBBackingStoreMap::const_iterator it =
    118         backing_store_map_.find(origin_url);
    119     DCHECK(it != backing_store_map_.end());
    120     ptr = it->second.get();
    121   }
    122   return ptr->HasOneRef();
    123 }
    124 
    125 void IndexedDBFactory::ForceClose(const GURL& origin_url) {
    126   std::pair<OriginDBMapIterator, OriginDBMapIterator> range =
    127       GetOpenDatabasesForOrigin(origin_url);
    128 
    129   while (range.first != range.second) {
    130     IndexedDBDatabase* db = range.first->second;
    131     ++range.first;
    132     db->ForceClose();
    133   }
    134 
    135   if (backing_store_map_.find(origin_url) != backing_store_map_.end())
    136     ReleaseBackingStore(origin_url, true /* immediate */);
    137 }
    138 
    139 void IndexedDBFactory::ContextDestroyed() {
    140   // Timers on backing stores hold a reference to this factory. When the
    141   // context (which nominally owns this factory) is destroyed during thread
    142   // termination the timers must be stopped so that this factory and the
    143   // stores can be disposed of.
    144   for (IndexedDBBackingStoreMap::iterator it = backing_store_map_.begin();
    145        it != backing_store_map_.end();
    146        ++it)
    147     it->second->close_timer()->Stop();
    148   backing_store_map_.clear();
    149   backing_stores_with_active_blobs_.clear();
    150   context_ = NULL;
    151 }
    152 
    153 void IndexedDBFactory::ReportOutstandingBlobs(const GURL& origin_url,
    154                                               bool blobs_outstanding) {
    155   if (!context_)
    156     return;
    157   if (blobs_outstanding) {
    158     DCHECK(!backing_stores_with_active_blobs_.count(origin_url));
    159     IndexedDBBackingStoreMap::iterator it = backing_store_map_.find(origin_url);
    160     if (it != backing_store_map_.end())
    161       backing_stores_with_active_blobs_.insert(*it);
    162     else
    163       DCHECK(false);
    164   } else {
    165     IndexedDBBackingStoreMap::iterator it =
    166         backing_stores_with_active_blobs_.find(origin_url);
    167     if (it != backing_stores_with_active_blobs_.end()) {
    168       backing_stores_with_active_blobs_.erase(it);
    169       ReleaseBackingStore(origin_url, false /* immediate */);
    170     }
    171   }
    172 }
    173 
    174 void IndexedDBFactory::GetDatabaseNames(
    175     scoped_refptr<IndexedDBCallbacks> callbacks,
    176     const GURL& origin_url,
    177     const base::FilePath& data_directory,
    178     net::URLRequestContext* request_context) {
    179   IDB_TRACE("IndexedDBFactory::GetDatabaseNames");
    180   // TODO(dgrogan): Plumb data_loss back to script eventually?
    181   blink::WebIDBDataLoss data_loss;
    182   std::string data_loss_message;
    183   bool disk_full;
    184   scoped_refptr<IndexedDBBackingStore> backing_store =
    185       OpenBackingStore(origin_url,
    186                        data_directory,
    187                        request_context,
    188                        &data_loss,
    189                        &data_loss_message,
    190                        &disk_full);
    191   if (!backing_store) {
    192     callbacks->OnError(
    193         IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
    194                                "Internal error opening backing store for "
    195                                "indexedDB.webkitGetDatabaseNames."));
    196     return;
    197   }
    198 
    199   leveldb::Status s;
    200   std::vector<base::string16> names = backing_store->GetDatabaseNames(&s);
    201   if (!s.ok()) {
    202     // TODO(cmumford): Handle this error
    203     DLOG(ERROR) << "Internal error getting database names";
    204   }
    205   callbacks->OnSuccess(names);
    206   backing_store = NULL;
    207   ReleaseBackingStore(origin_url, false /* immediate */);
    208 }
    209 
    210 void IndexedDBFactory::DeleteDatabase(
    211     const base::string16& name,
    212     net::URLRequestContext* request_context,
    213     scoped_refptr<IndexedDBCallbacks> callbacks,
    214     const GURL& origin_url,
    215     const base::FilePath& data_directory) {
    216   IDB_TRACE("IndexedDBFactory::DeleteDatabase");
    217   IndexedDBDatabase::Identifier unique_identifier(origin_url, name);
    218   IndexedDBDatabaseMap::iterator it = database_map_.find(unique_identifier);
    219   if (it != database_map_.end()) {
    220     // If there are any connections to the database, directly delete the
    221     // database.
    222     it->second->DeleteDatabase(callbacks);
    223     return;
    224   }
    225 
    226   // TODO(dgrogan): Plumb data_loss back to script eventually?
    227   blink::WebIDBDataLoss data_loss;
    228   std::string data_loss_message;
    229   bool disk_full = false;
    230   scoped_refptr<IndexedDBBackingStore> backing_store =
    231       OpenBackingStore(origin_url,
    232                        data_directory,
    233                        request_context,
    234                        &data_loss,
    235                        &data_loss_message,
    236                        &disk_full);
    237   if (!backing_store) {
    238     callbacks->OnError(
    239         IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
    240                                ASCIIToUTF16(
    241                                    "Internal error opening backing store "
    242                                    "for indexedDB.deleteDatabase.")));
    243     return;
    244   }
    245 
    246   leveldb::Status s;
    247   scoped_refptr<IndexedDBDatabase> database = IndexedDBDatabase::Create(
    248       name, backing_store, this, unique_identifier, &s);
    249   if (!database) {
    250     IndexedDBDatabaseError error(
    251         blink::WebIDBDatabaseExceptionUnknownError,
    252         ASCIIToUTF16(
    253             "Internal error creating database backend for "
    254             "indexedDB.deleteDatabase."));
    255     callbacks->OnError(error);
    256     if (leveldb_env::IsCorruption(s))
    257       HandleBackingStoreCorruption(origin_url, error);
    258     return;
    259   }
    260 
    261   database_map_[unique_identifier] = database;
    262   origin_dbs_.insert(std::make_pair(origin_url, database));
    263   database->DeleteDatabase(callbacks);
    264   RemoveDatabaseFromMaps(unique_identifier);
    265   database = NULL;
    266   backing_store = NULL;
    267   ReleaseBackingStore(origin_url, false /* immediate */);
    268 }
    269 
    270 void IndexedDBFactory::DatabaseDeleted(
    271     const IndexedDBDatabase::Identifier& identifier) {
    272   // NULL after ContextDestroyed() called, and in some unit tests.
    273   if (!context_)
    274     return;
    275   context_->DatabaseDeleted(identifier.first);
    276 }
    277 
    278 void IndexedDBFactory::HandleBackingStoreFailure(const GURL& origin_url) {
    279   // NULL after ContextDestroyed() called, and in some unit tests.
    280   if (!context_)
    281     return;
    282   context_->ForceClose(origin_url,
    283                        IndexedDBContextImpl::FORCE_CLOSE_BACKING_STORE_FAILURE);
    284 }
    285 
    286 void IndexedDBFactory::HandleBackingStoreCorruption(
    287     const GURL& origin_url,
    288     const IndexedDBDatabaseError& error) {
    289   // Make a copy of origin_url as this is likely a reference to a member of a
    290   // backing store which this function will be deleting.
    291   GURL saved_origin_url(origin_url);
    292   DCHECK(context_);
    293   base::FilePath path_base = context_->data_path();
    294   IndexedDBBackingStore::RecordCorruptionInfo(
    295       path_base, saved_origin_url, base::UTF16ToUTF8(error.message()));
    296   HandleBackingStoreFailure(saved_origin_url);
    297   // Note: DestroyBackingStore only deletes LevelDB files, leaving all others,
    298   //       so our corruption info file will remain.
    299   leveldb::Status s =
    300       IndexedDBBackingStore::DestroyBackingStore(path_base, saved_origin_url);
    301   if (!s.ok())
    302     DLOG(ERROR) << "Unable to delete backing store: " << s.ToString();
    303 }
    304 
    305 bool IndexedDBFactory::IsDatabaseOpen(const GURL& origin_url,
    306                                       const base::string16& name) const {
    307   return !!database_map_.count(IndexedDBDatabase::Identifier(origin_url, name));
    308 }
    309 
    310 bool IndexedDBFactory::IsBackingStoreOpen(const GURL& origin_url) const {
    311   return backing_store_map_.find(origin_url) != backing_store_map_.end();
    312 }
    313 
    314 bool IndexedDBFactory::IsBackingStorePendingClose(const GURL& origin_url)
    315     const {
    316   IndexedDBBackingStoreMap::const_iterator it =
    317       backing_store_map_.find(origin_url);
    318   if (it == backing_store_map_.end())
    319     return false;
    320   return it->second->close_timer()->IsRunning();
    321 }
    322 
    323 scoped_refptr<IndexedDBBackingStore> IndexedDBFactory::OpenBackingStoreHelper(
    324     const GURL& origin_url,
    325     const base::FilePath& data_directory,
    326     net::URLRequestContext* request_context,
    327     blink::WebIDBDataLoss* data_loss,
    328     std::string* data_loss_message,
    329     bool* disk_full,
    330     bool first_time) {
    331   return IndexedDBBackingStore::Open(this,
    332                                      origin_url,
    333                                      data_directory,
    334                                      request_context,
    335                                      data_loss,
    336                                      data_loss_message,
    337                                      disk_full,
    338                                      context_->TaskRunner(),
    339                                      first_time);
    340 }
    341 
    342 scoped_refptr<IndexedDBBackingStore> IndexedDBFactory::OpenBackingStore(
    343     const GURL& origin_url,
    344     const base::FilePath& data_directory,
    345     net::URLRequestContext* request_context,
    346     blink::WebIDBDataLoss* data_loss,
    347     std::string* data_loss_message,
    348     bool* disk_full) {
    349   const bool open_in_memory = data_directory.empty();
    350 
    351   IndexedDBBackingStoreMap::iterator it2 = backing_store_map_.find(origin_url);
    352   if (it2 != backing_store_map_.end()) {
    353     it2->second->close_timer()->Stop();
    354     return it2->second;
    355   }
    356 
    357   scoped_refptr<IndexedDBBackingStore> backing_store;
    358   bool first_time = false;
    359   if (open_in_memory) {
    360     backing_store =
    361         IndexedDBBackingStore::OpenInMemory(origin_url, context_->TaskRunner());
    362   } else {
    363     first_time = !backends_opened_since_boot_.count(origin_url);
    364 
    365     backing_store = OpenBackingStoreHelper(origin_url,
    366                                            data_directory,
    367                                            request_context,
    368                                            data_loss,
    369                                            data_loss_message,
    370                                            disk_full,
    371                                            first_time);
    372   }
    373 
    374   if (backing_store.get()) {
    375     if (first_time)
    376       backends_opened_since_boot_.insert(origin_url);
    377     backing_store_map_[origin_url] = backing_store;
    378     // If an in-memory database, bind lifetime to this factory instance.
    379     if (open_in_memory)
    380       session_only_backing_stores_.insert(backing_store);
    381 
    382     // All backing stores associated with this factory should be of the same
    383     // type.
    384     DCHECK_NE(session_only_backing_stores_.empty(), open_in_memory);
    385 
    386     return backing_store;
    387   }
    388 
    389   return 0;
    390 }
    391 
    392 void IndexedDBFactory::Open(const base::string16& name,
    393                             const IndexedDBPendingConnection& connection,
    394                             net::URLRequestContext* request_context,
    395                             const GURL& origin_url,
    396                             const base::FilePath& data_directory) {
    397   IDB_TRACE("IndexedDBFactory::Open");
    398   scoped_refptr<IndexedDBDatabase> database;
    399   IndexedDBDatabase::Identifier unique_identifier(origin_url, name);
    400   IndexedDBDatabaseMap::iterator it = database_map_.find(unique_identifier);
    401   blink::WebIDBDataLoss data_loss =
    402       blink::WebIDBDataLossNone;
    403   std::string data_loss_message;
    404   bool disk_full = false;
    405   bool was_open = (it != database_map_.end());
    406   if (!was_open) {
    407     scoped_refptr<IndexedDBBackingStore> backing_store =
    408         OpenBackingStore(origin_url,
    409                          data_directory,
    410                          request_context,
    411                          &data_loss,
    412                          &data_loss_message,
    413                          &disk_full);
    414     if (!backing_store) {
    415       if (disk_full) {
    416         connection.callbacks->OnError(
    417             IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionQuotaError,
    418                                    ASCIIToUTF16(
    419                                        "Encountered full disk while opening "
    420                                        "backing store for indexedDB.open.")));
    421         return;
    422       }
    423       connection.callbacks->OnError(IndexedDBDatabaseError(
    424           blink::WebIDBDatabaseExceptionUnknownError,
    425           ASCIIToUTF16(
    426               "Internal error opening backing store for indexedDB.open.")));
    427       return;
    428     }
    429 
    430     leveldb::Status s;
    431     database = IndexedDBDatabase::Create(
    432         name, backing_store, this, unique_identifier, &s);
    433     if (!database) {
    434       DLOG(ERROR) << "Unable to create the database";
    435       IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
    436                                    ASCIIToUTF16(
    437                                        "Internal error creating "
    438                                        "database backend for "
    439                                        "indexedDB.open."));
    440       connection.callbacks->OnError(error);
    441       if (leveldb_env::IsCorruption(s)) {
    442         backing_store = NULL;  // Closes the LevelDB so that it can be deleted
    443         HandleBackingStoreCorruption(origin_url, error);
    444       }
    445       return;
    446     }
    447   } else {
    448     database = it->second;
    449   }
    450 
    451   if (data_loss != blink::WebIDBDataLossNone)
    452     connection.callbacks->OnDataLoss(data_loss, data_loss_message);
    453 
    454   database->OpenConnection(connection);
    455 
    456   if (!was_open && database->ConnectionCount() > 0) {
    457     database_map_[unique_identifier] = database;
    458     origin_dbs_.insert(std::make_pair(origin_url, database));
    459   }
    460 }
    461 
    462 std::pair<IndexedDBFactory::OriginDBMapIterator,
    463           IndexedDBFactory::OriginDBMapIterator>
    464 IndexedDBFactory::GetOpenDatabasesForOrigin(const GURL& origin_url) const {
    465   return origin_dbs_.equal_range(origin_url);
    466 }
    467 
    468 size_t IndexedDBFactory::GetConnectionCount(const GURL& origin_url) const {
    469   size_t count(0);
    470 
    471   std::pair<OriginDBMapIterator, OriginDBMapIterator> range =
    472       GetOpenDatabasesForOrigin(origin_url);
    473   for (OriginDBMapIterator it = range.first; it != range.second; ++it)
    474     count += it->second->ConnectionCount();
    475 
    476   return count;
    477 }
    478 
    479 }  // namespace content
    480