diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/storage/StorageAreaSync.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/storage/StorageAreaSync.cpp')
-rw-r--r-- | Source/WebCore/storage/StorageAreaSync.cpp | 531 |
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 |