summaryrefslogtreecommitdiff
path: root/Source/WebCore/storage/StorageAreaSync.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/storage/StorageAreaSync.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/storage/StorageAreaSync.cpp')
-rw-r--r--Source/WebCore/storage/StorageAreaSync.cpp531
1 files changed, 0 insertions, 531 deletions
diff --git a/Source/WebCore/storage/StorageAreaSync.cpp b/Source/WebCore/storage/StorageAreaSync.cpp
deleted file mode 100644
index eea5ebe6f..000000000
--- a/Source/WebCore/storage/StorageAreaSync.cpp
+++ /dev/null
@@ -1,531 +0,0 @@
-/*
- * Copyright (C) 2008, 2009, 2010 Apple Inc. All Rights Reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "config.h"
-#include "StorageAreaSync.h"
-
-#include "EventNames.h"
-#include "FileSystem.h"
-#include "HTMLElement.h"
-#include "SQLiteDatabaseTracker.h"
-#include "SQLiteFileSystem.h"
-#include "SQLiteStatement.h"
-#include "SQLiteTransaction.h"
-#include "SecurityOrigin.h"
-#include "StorageAreaImpl.h"
-#include "StorageSyncManager.h"
-#include "StorageTracker.h"
-#include "SuddenTermination.h"
-#include <wtf/Functional.h>
-#include <wtf/MainThread.h>
-#include <wtf/text/CString.h>
-
-namespace WebCore {
-
-// If the StorageArea undergoes rapid changes, don't sync each change to disk.
-// Instead, queue up a batch of items to sync and actually do the sync at the following interval.
-static const double StorageSyncInterval = 1.0;
-
-// A sane limit on how many items we'll schedule to sync all at once. This makes it
-// much harder to starve the rest of LocalStorage and the OS's IO subsystem in general.
-static const int MaxiumItemsToSync = 100;
-
-inline StorageAreaSync::StorageAreaSync(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, const String& databaseIdentifier)
- : m_syncTimer(this, &StorageAreaSync::syncTimerFired)
- , m_itemsCleared(false)
- , m_finalSyncScheduled(false)
- , m_storageArea(storageArea)
- , m_syncManager(storageSyncManager)
- , m_databaseIdentifier(databaseIdentifier.isolatedCopy())
- , m_clearItemsWhileSyncing(false)
- , m_syncScheduled(false)
- , m_syncInProgress(false)
- , m_databaseOpenFailed(false)
- , m_syncCloseDatabase(false)
- , m_importComplete(false)
-{
- ASSERT(isMainThread());
- ASSERT(m_storageArea);
- ASSERT(m_syncManager);
-
- // FIXME: If it can't import, then the default WebKit behavior should be that of private browsing,
- // not silently ignoring it. https://bugs.webkit.org/show_bug.cgi?id=25894
- m_syncManager->dispatch(bind(&StorageAreaSync::performImport, this));
-}
-
-PassRefPtr<StorageAreaSync> StorageAreaSync::create(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, const String& databaseIdentifier)
-{
- RefPtr<StorageAreaSync> area = adoptRef(new StorageAreaSync(storageSyncManager, storageArea, databaseIdentifier));
-
- return area.release();
-}
-
-StorageAreaSync::~StorageAreaSync()
-{
- ASSERT(isMainThread());
- ASSERT(!m_syncTimer.isActive());
- ASSERT(m_finalSyncScheduled);
-}
-
-void StorageAreaSync::scheduleFinalSync()
-{
- ASSERT(isMainThread());
- // FIXME: We do this to avoid races, but it'd be better to make things safe without blocking.
- blockUntilImportComplete();
- m_storageArea = 0; // This is done in blockUntilImportComplete() but this is here as a form of documentation that we must be absolutely sure the ref count cycle is broken.
-
- if (m_syncTimer.isActive())
- m_syncTimer.stop();
- else {
- // The following is balanced by the call to enableSuddenTermination in the
- // syncTimerFired function.
- disableSuddenTermination();
- }
- // FIXME: This is synchronous. We should do it on the background process, but
- // we should do it safely.
- m_finalSyncScheduled = true;
- syncTimerFired(&m_syncTimer);
-
- m_syncManager->dispatch(bind(&StorageAreaSync::deleteEmptyDatabase, this));
-}
-
-void StorageAreaSync::scheduleItemForSync(const String& key, const String& value)
-{
- ASSERT(isMainThread());
- ASSERT(!m_finalSyncScheduled);
-
- m_changedItems.set(key, value);
- if (!m_syncTimer.isActive()) {
- m_syncTimer.startOneShot(StorageSyncInterval);
-
- // The following is balanced by the call to enableSuddenTermination in the
- // syncTimerFired function.
- disableSuddenTermination();
- }
-}
-
-void StorageAreaSync::scheduleClear()
-{
- ASSERT(isMainThread());
- ASSERT(!m_finalSyncScheduled);
-
- m_changedItems.clear();
- m_itemsCleared = true;
- if (!m_syncTimer.isActive()) {
- m_syncTimer.startOneShot(StorageSyncInterval);
-
- // The following is balanced by the call to enableSuddenTermination in the
- // syncTimerFired function.
- disableSuddenTermination();
- }
-}
-
-void StorageAreaSync::scheduleCloseDatabase()
-{
- ASSERT(isMainThread());
- ASSERT(!m_finalSyncScheduled);
-
- if (!m_database.isOpen())
- return;
-
- m_syncCloseDatabase = true;
-
- if (!m_syncTimer.isActive()) {
- m_syncTimer.startOneShot(StorageSyncInterval);
-
- // The following is balanced by the call to enableSuddenTermination in the
- // syncTimerFired function.
- disableSuddenTermination();
- }
-}
-
-void StorageAreaSync::syncTimerFired(Timer<StorageAreaSync>*)
-{
- ASSERT(isMainThread());
-
- bool partialSync = false;
- {
- MutexLocker locker(m_syncLock);
-
- // Do not schedule another sync if we're still trying to complete the
- // previous one. But, if we're shutting down, schedule it anyway.
- if (m_syncInProgress && !m_finalSyncScheduled) {
- ASSERT(!m_syncTimer.isActive());
- m_syncTimer.startOneShot(StorageSyncInterval);
- return;
- }
-
- if (m_itemsCleared) {
- m_itemsPendingSync.clear();
- m_clearItemsWhileSyncing = true;
- m_itemsCleared = false;
- }
-
- HashMap<String, String>::iterator changed_it = m_changedItems.begin();
- HashMap<String, String>::iterator changed_end = m_changedItems.end();
- for (int count = 0; changed_it != changed_end; ++count, ++changed_it) {
- if (count >= MaxiumItemsToSync && !m_finalSyncScheduled) {
- partialSync = true;
- break;
- }
- m_itemsPendingSync.set(changed_it->key.isolatedCopy(), changed_it->value.isolatedCopy());
- }
-
- if (partialSync) {
- // We can't do the fast path of simply clearing all items, so we'll need to manually
- // remove them one by one. Done under lock since m_itemsPendingSync is modified by
- // the background thread.
- HashMap<String, String>::iterator pending_it = m_itemsPendingSync.begin();
- HashMap<String, String>::iterator pending_end = m_itemsPendingSync.end();
- for (; pending_it != pending_end; ++pending_it)
- m_changedItems.remove(pending_it->key);
- }
-
- if (!m_syncScheduled) {
- m_syncScheduled = true;
-
- // The following is balanced by the call to enableSuddenTermination in the
- // performSync function.
- disableSuddenTermination();
-
- m_syncManager->dispatch(bind(&StorageAreaSync::performSync, this));
- }
- }
-
- if (partialSync) {
- // If we didn't finish syncing, then we need to finish the job later.
- ASSERT(!m_syncTimer.isActive());
- m_syncTimer.startOneShot(StorageSyncInterval);
- } else {
- // The following is balanced by the calls to disableSuddenTermination in the
- // scheduleItemForSync, scheduleClear, and scheduleFinalSync functions.
- enableSuddenTermination();
-
- m_changedItems.clear();
- }
-}
-
-void StorageAreaSync::openDatabase(OpenDatabaseParamType openingStrategy)
-{
- ASSERT(!isMainThread());
- ASSERT(!m_database.isOpen());
- ASSERT(!m_databaseOpenFailed);
-
- SQLiteTransactionInProgressAutoCounter transactionCounter;
-
- String databaseFilename = m_syncManager->fullDatabaseFilename(m_databaseIdentifier);
-
- if (!fileExists(databaseFilename) && openingStrategy == SkipIfNonExistent)
- return;
-
- if (databaseFilename.isEmpty()) {
- LOG_ERROR("Filename for local storage database is empty - cannot open for persistent storage");
- markImported();
- m_databaseOpenFailed = true;
- return;
- }
-
- // A StorageTracker thread may have been scheduled to delete the db we're
- // reopening, so cancel possible deletion.
- StorageTracker::tracker().cancelDeletingOrigin(m_databaseIdentifier);
-
- if (!m_database.open(databaseFilename)) {
- LOG_ERROR("Failed to open database file %s for local storage", databaseFilename.utf8().data());
- markImported();
- m_databaseOpenFailed = true;
- return;
- }
-
- migrateItemTableIfNeeded();
-
- if (!m_database.executeCommand("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)")) {
- LOG_ERROR("Failed to create table ItemTable for local storage");
- markImported();
- m_databaseOpenFailed = true;
- return;
- }
-
- StorageTracker::tracker().setOriginDetails(m_databaseIdentifier, databaseFilename);
-}
-
-void StorageAreaSync::migrateItemTableIfNeeded()
-{
- if (!m_database.tableExists("ItemTable"))
- return;
-
- {
- SQLiteStatement query(m_database, "SELECT value FROM ItemTable LIMIT 1");
- // this query isn't ever executed.
- if (query.isColumnDeclaredAsBlob(0))
- return;
- }
-
- // alter table for backward compliance, change the value type from TEXT to BLOB.
- static const char* commands[] = {
- "DROP TABLE IF EXISTS ItemTable2",
- "CREATE TABLE ItemTable2 (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)",
- "INSERT INTO ItemTable2 SELECT * from ItemTable",
- "DROP TABLE ItemTable",
- "ALTER TABLE ItemTable2 RENAME TO ItemTable",
- 0,
- };
-
- SQLiteTransaction transaction(m_database, false);
- transaction.begin();
- for (size_t i = 0; commands[i]; ++i) {
- if (!m_database.executeCommand(commands[i])) {
- LOG_ERROR("Failed to migrate table ItemTable for local storage when executing: %s", commands[i]);
- transaction.rollback();
-
- // finally it will try to keep a backup of ItemTable for the future restoration.
- // NOTICE: this will essentially DELETE the current database, but that's better
- // than continually hitting this case and never being able to use the local storage.
- // if this is ever hit, it's definitely a bug.
- ASSERT_NOT_REACHED();
- if (!m_database.executeCommand("ALTER TABLE ItemTable RENAME TO Backup_ItemTable"))
- LOG_ERROR("Failed to save ItemTable after migration job failed.");
-
- return;
- }
- }
- transaction.commit();
-}
-
-void StorageAreaSync::performImport()
-{
- ASSERT(!isMainThread());
- ASSERT(!m_database.isOpen());
-
- openDatabase(SkipIfNonExistent);
- if (!m_database.isOpen()) {
- markImported();
- return;
- }
-
- SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable");
- if (query.prepare() != SQLResultOk) {
- LOG_ERROR("Unable to select items from ItemTable for local storage");
- markImported();
- return;
- }
-
- HashMap<String, String> itemMap;
-
- int result = query.step();
- while (result == SQLResultRow) {
- itemMap.set(query.getColumnText(0), query.getColumnBlobAsString(1));
- result = query.step();
- }
-
- if (result != SQLResultDone) {
- LOG_ERROR("Error reading items from ItemTable for local storage");
- markImported();
- return;
- }
-
- m_storageArea->importItems(itemMap);
-
- markImported();
-}
-
-void StorageAreaSync::markImported()
-{
- MutexLocker locker(m_importLock);
- m_importComplete = true;
- m_importCondition.signal();
-}
-
-// FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
-// Blocking everything until the import is complete is by far the simplest and safest thing to do, but
-// there is certainly room for safe optimization: Key/length will never be able to make use of such an
-// optimization (since the order of iteration can change as items are being added). Get can return any
-// item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
-// of items the import should not overwrite. Clear can also work, but it'll need to kill the import
-// job first.
-void StorageAreaSync::blockUntilImportComplete()
-{
- ASSERT(isMainThread());
-
- // Fast path. We set m_storageArea to 0 only after m_importComplete being true.
- if (!m_storageArea)
- return;
-
- MutexLocker locker(m_importLock);
- while (!m_importComplete)
- m_importCondition.wait(m_importLock);
- m_storageArea = 0;
-}
-
-void StorageAreaSync::sync(bool clearItems, const HashMap<String, String>& items)
-{
- ASSERT(!isMainThread());
-
- if (items.isEmpty() && !clearItems && !m_syncCloseDatabase)
- return;
- if (m_databaseOpenFailed)
- return;
-
- if (!m_database.isOpen() && m_syncCloseDatabase) {
- m_syncCloseDatabase = false;
- return;
- }
-
- if (!m_database.isOpen())
- openDatabase(CreateIfNonExistent);
- if (!m_database.isOpen())
- return;
-
- // Closing this db because it is about to be deleted by StorageTracker.
- // The delete will be cancelled if StorageAreaSync needs to reopen the db
- // to write new items created after the request to delete the db.
- if (m_syncCloseDatabase) {
- m_syncCloseDatabase = false;
- m_database.close();
- return;
- }
-
- SQLiteTransactionInProgressAutoCounter transactionCounter;
-
- // If the clear flag is set, then we clear all items out before we write any new ones in.
- if (clearItems) {
- SQLiteStatement clear(m_database, "DELETE FROM ItemTable");
- if (clear.prepare() != SQLResultOk) {
- LOG_ERROR("Failed to prepare clear statement - cannot write to local storage database");
- return;
- }
-
- int result = clear.step();
- if (result != SQLResultDone) {
- LOG_ERROR("Failed to clear all items in the local storage database - %i", result);
- return;
- }
- }
-
- SQLiteStatement insert(m_database, "INSERT INTO ItemTable VALUES (?, ?)");
- if (insert.prepare() != SQLResultOk) {
- LOG_ERROR("Failed to prepare insert statement - cannot write to local storage database");
- return;
- }
-
- SQLiteStatement remove(m_database, "DELETE FROM ItemTable WHERE key=?");
- if (remove.prepare() != SQLResultOk) {
- LOG_ERROR("Failed to prepare delete statement - cannot write to local storage database");
- return;
- }
-
- HashMap<String, String>::const_iterator end = items.end();
-
- SQLiteTransaction transaction(m_database);
- transaction.begin();
- for (HashMap<String, String>::const_iterator it = items.begin(); it != end; ++it) {
- // Based on the null-ness of the second argument, decide whether this is an insert or a delete.
- SQLiteStatement& query = it->value.isNull() ? remove : insert;
-
- query.bindText(1, it->key);
-
- // If the second argument is non-null, we're doing an insert, so bind it as the value.
- if (!it->value.isNull())
- query.bindBlob(2, it->value);
-
- int result = query.step();
- if (result != SQLResultDone) {
- LOG_ERROR("Failed to update item in the local storage database - %i", result);
- break;
- }
-
- query.reset();
- }
- transaction.commit();
-}
-
-void StorageAreaSync::performSync()
-{
- ASSERT(!isMainThread());
-
- bool clearItems;
- HashMap<String, String> items;
- {
- MutexLocker locker(m_syncLock);
-
- ASSERT(m_syncScheduled);
-
- clearItems = m_clearItemsWhileSyncing;
- m_itemsPendingSync.swap(items);
-
- m_clearItemsWhileSyncing = false;
- m_syncScheduled = false;
- m_syncInProgress = true;
- }
-
- sync(clearItems, items);
-
- {
- MutexLocker locker(m_syncLock);
- m_syncInProgress = false;
- }
-
- // The following is balanced by the call to disableSuddenTermination in the
- // syncTimerFired function.
- enableSuddenTermination();
-}
-
-void StorageAreaSync::deleteEmptyDatabase()
-{
- ASSERT(!isMainThread());
- if (!m_database.isOpen())
- return;
-
- SQLiteStatement query(m_database, "SELECT COUNT(*) FROM ItemTable");
- if (query.prepare() != SQLResultOk) {
- LOG_ERROR("Unable to count number of rows in ItemTable for local storage");
- return;
- }
-
- int result = query.step();
- if (result != SQLResultRow) {
- LOG_ERROR("No results when counting number of rows in ItemTable for local storage");
- return;
- }
-
- int count = query.getColumnInt(0);
- if (!count) {
- query.finalize();
- m_database.close();
- if (StorageTracker::tracker().isActive())
- StorageTracker::tracker().deleteOriginWithIdentifier(m_databaseIdentifier);
- else {
- String databaseFilename = m_syncManager->fullDatabaseFilename(m_databaseIdentifier);
- if (!SQLiteFileSystem::deleteDatabaseFile(databaseFilename))
- LOG_ERROR("Failed to delete database file %s\n", databaseFilename.utf8().data());
- }
- }
-}
-
-void StorageAreaSync::scheduleSync()
-{
- syncTimerFired(&m_syncTimer);
-}
-
-} // namespace WebCore