Improve robustness of WebSQL quota management.
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 5 Mar 2013 15:54:13 +0000 (15:54 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 5 Mar 2013 15:54:13 +0000 (15:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=110600.

Reviewed by Geoffrey Garen.

Source/WebCore:

1. Introduced the OriginLock for synchronizing write access to the
   database origin directory. This allows us to more accurately
   compute the disk usage.

   The OriginLock uses a mutex to provide mutual exclusion between
   threads and a file lock for mutual exclusion between processes.
   The file lock part is conditional on USE(FILE_LOCK).

   The mutex mutual exclusion also serves to ensure that only 1 thread
   can write to a sqlite database at one time.

2. Change the SQLTransactionCoordinator to only allow one write
   transaction to an origin instead of one write transaction per
   database. This is needed in order to accurately compute the
   disk usage. It is also necessary so that the OriginLock does not
   deadlock itself (as would be the case if concurrent write transactions
   to different databases in the same origin are allowed).

3. Fix DatabaseTracker::getMaxSizeForDatabase() to check for when
   disk usage may exceed the quota, and ensure that we will return
   an appropriate max database size.

   Disk usage can exceed the usage if it is already near the quota limit
   but have not exceeded it yet. If a new database is opened in that
   origin, it may bump the usage above the quota, but should not
   continually repeat this. Subsequent attempts to open a database
   will find that the quota is already exhausted and fail.

   There is still a race condition pertaining to the tracker database
   getting out of sync that may still enable runaway growth in the
   database sizes. That issue only manifest in a multi-process
   environment, and will be fixed in another changeset.

4. Fixed a bug in SQLStatement to check if the errorCallback exists
   before invoking it.

No new layout tests. A quota-test.html was attached to bugzilla for manual
testing of multi-tab concurrent consumption of storage resource, and also
to test handling situations when the user deletes the database files while
the script is still using the database.

* CMakeLists.txt:
* GNUmakefile.list.am:
* Modules/webdatabase/DatabaseTracker.cpp:
(WebCore::DatabaseTracker::getMaxSizeForDatabase):
(WebCore::DatabaseTracker::originLockFor):
(WebCore::DatabaseTracker::deleteOriginLockFor):
(WebCore::DatabaseTracker::deleteOrigin):
* Modules/webdatabase/DatabaseTracker.h:
* Modules/webdatabase/OriginLock.cpp: Added.
(WebCore::OriginLock::lockFileNameForPath):
(WebCore::OriginLock::OriginLock):
(WebCore::OriginLock::~OriginLock):
(WebCore::OriginLock::lock):
(WebCore::OriginLock::unlock):
* Modules/webdatabase/OriginLock.h: Added.
* Modules/webdatabase/SQLStatement.cpp:
(WebCore::SQLStatement::performCallback):
* Modules/webdatabase/SQLTransactionBackend.cpp:
(WebCore::SQLTransactionBackend::doCleanup):
(WebCore::SQLTransactionBackend::computeNextStateAndCleanupIfNeeded):
(WebCore::SQLTransactionBackend::openTransactionAndPreflight):
(WebCore::SQLTransactionBackend::postflightAndCommit):
(WebCore::SQLTransactionBackend::cleanupAfterTransactionErrorCallback):
(WebCore::SQLTransactionBackend::acquireOriginLock):
(WebCore::SQLTransactionBackend::releaseOriginLockIfNeeded):
* Modules/webdatabase/SQLTransactionBackend.h:
(SQLTransactionBackend):
* Modules/webdatabase/SQLTransactionCoordinator.cpp:
(WebCore::getDatabaseIdentifier):
* Target.pri:
* WebCore.gypi:
* WebCore.vcproj/WebCore.vcproj:
* WebCore.vcxproj/WebCore.vcxproj:
* WebCore.vcxproj/WebCore.vcxproj.filters:
* WebCore.xcodeproj/project.pbxproj:
* config.h:
* platform/FileSystem.h:
* platform/posix/FileSystemPOSIX.cpp:
(WebCore::lockFile):
(WebCore::unlockFile):

LayoutTests:

* storage/websql/multiple-databases-garbage-collection.js:
- This test runs 2 transactions on 2 databases (1 each). The 2 databases
  are named "persistent" and "forgotten". The test executes the
  transaction on "persistent" first, but expects the transaction on
  "forgotten" to finish first. This is because "forgotten"'s transaction
  is a smaller one. The new changes to SQLTransactionCoordinator now
  ensures that a write transaction must completes before another is
  started for databases in the same origin. Hence, the previously expected
  result will no longer be true.

  Regardless, the purpose of the test is not to test the order of
  completion but that resources are reclaimed. So, I'm changing the test
  to start the "forgotten" transaction first followed by the "persistent"
  transaction. This ensures that the test will yield consistent results
  even when run on ports that may allow more than one write transaction
  to run at the same time.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@144760 268f45cc-cd09-0410-ab3c-d52691b4dbfc

23 files changed:
LayoutTests/ChangeLog
LayoutTests/storage/websql/multiple-databases-garbage-collection.js
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/GNUmakefile.list.am
Source/WebCore/Modules/webdatabase/DatabaseTracker.cpp
Source/WebCore/Modules/webdatabase/DatabaseTracker.h
Source/WebCore/Modules/webdatabase/OriginLock.cpp [new file with mode: 0644]
Source/WebCore/Modules/webdatabase/OriginLock.h [new file with mode: 0644]
Source/WebCore/Modules/webdatabase/SQLStatement.cpp
Source/WebCore/Modules/webdatabase/SQLTransactionBackend.cpp
Source/WebCore/Modules/webdatabase/SQLTransactionBackend.h
Source/WebCore/Modules/webdatabase/SQLTransactionCoordinator.cpp
Source/WebCore/Target.pri
Source/WebCore/WebCore.gyp/WebCore.gyp
Source/WebCore/WebCore.gypi
Source/WebCore/WebCore.vcproj/WebCore.vcproj
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/config.h
Source/WebCore/platform/FileSystem.h
Source/WebCore/platform/posix/FileSystemPOSIX.cpp

index 66bd1cc..5d6b8bf 100644 (file)
@@ -1,3 +1,27 @@
+2013-03-05  Mark Lam  <mark.lam@apple.com>
+
+        Improve robustness of WebSQL quota management.
+        https://bugs.webkit.org/show_bug.cgi?id=110600.
+
+        Reviewed by Geoffrey Garen.
+
+        * storage/websql/multiple-databases-garbage-collection.js:
+        - This test runs 2 transactions on 2 databases (1 each). The 2 databases
+          are named "persistent" and "forgotten". The test executes the
+          transaction on "persistent" first, but expects the transaction on
+          "forgotten" to finish first. This is because "forgotten"'s transaction
+          is a smaller one. The new changes to SQLTransactionCoordinator now
+          ensures that a write transaction must completes before another is
+          started for databases in the same origin. Hence, the previously expected
+          result will no longer be true.
+
+          Regardless, the purpose of the test is not to test the order of
+          completion but that resources are reclaimed. So, I'm changing the test
+          to start the "forgotten" transaction first followed by the "persistent"
+          transaction. This ensures that the test will yield consistent results
+          even when run on ports that may allow more than one write transaction
+          to run at the same time.
+
 2013-03-05  Andrey Kosyakov  <caseq@chromium.org>
 
         Web Inspector: remove length parameter from Parse HTML timeline event
index 25680ea..bd2f400 100644 (file)
@@ -27,19 +27,6 @@ function runTest()
     persistentDB = openDatabaseWithSuffix("MultipleDatabasesTest1", "1.0", "Test one out of a set of databases being destroyed (1)", 32768);
     forgottenDB = openDatabaseWithSuffix("MultipleDatabasesTest2", "1.0", "Test one out of a set of databases being destroyed (2)", 32768);
 
-    persistentDB.transaction(function(tx) {
-        tx.executeSql("CREATE TABLE IF NOT EXISTS DataTest (randomData)", [], function(tx, result) { 
-            for (var i = 0; i < 25; ++i)
-                tx.executeSql("INSERT INTO DataTest (randomData) VALUES (1)", []);
-        }); 
-    }, function(err) {
-        log("Persistent Database Transaction Errored - " + err);
-        checkCompletion();
-    }, function() {
-        log("Persistent Database Transaction Complete");
-        checkCompletion();
-    });
-
     forgottenDB.transaction(function(tx) {
         tx.executeSql("CREATE TABLE IF NOT EXISTS EmptyTable (unimportantData)", []);
     }, function(err) {
@@ -53,4 +40,17 @@ function runTest()
         GC();
         checkCompletion();
     });
+
+    persistentDB.transaction(function(tx) {
+        tx.executeSql("CREATE TABLE IF NOT EXISTS DataTest (randomData)", [], function(tx, result) { 
+            for (var i = 0; i < 25; ++i)
+                tx.executeSql("INSERT INTO DataTest (randomData) VALUES (1)", []);
+        }); 
+    }, function(err) {
+        log("Persistent Database Transaction Errored - " + err);
+        checkCompletion();
+    }, function() {
+        log("Persistent Database Transaction Complete");
+        checkCompletion();
+    });
 }
index 049dc6a..1868e64 100644 (file)
@@ -954,6 +954,7 @@ set(WebCore_SOURCES
     Modules/webdatabase/DatabaseTask.cpp
     Modules/webdatabase/DatabaseThread.cpp
     Modules/webdatabase/DatabaseTracker.cpp
+    Modules/webdatabase/OriginLock.cpp
     Modules/webdatabase/SQLException.cpp
     Modules/webdatabase/SQLResultSet.cpp
     Modules/webdatabase/SQLResultSetRowList.cpp
index d2433b5..f48b388 100644 (file)
@@ -1,3 +1,92 @@
+2013-03-05  Mark Lam  <mark.lam@apple.com>
+
+        Improve robustness of WebSQL quota management.
+        https://bugs.webkit.org/show_bug.cgi?id=110600.
+
+        Reviewed by Geoffrey Garen.
+
+        1. Introduced the OriginLock for synchronizing write access to the
+           database origin directory. This allows us to more accurately
+           compute the disk usage.
+
+           The OriginLock uses a mutex to provide mutual exclusion between
+           threads and a file lock for mutual exclusion between processes.
+           The file lock part is conditional on USE(FILE_LOCK).
+
+           The mutex mutual exclusion also serves to ensure that only 1 thread
+           can write to a sqlite database at one time.
+
+        2. Change the SQLTransactionCoordinator to only allow one write
+           transaction to an origin instead of one write transaction per
+           database. This is needed in order to accurately compute the
+           disk usage. It is also necessary so that the OriginLock does not
+           deadlock itself (as would be the case if concurrent write transactions
+           to different databases in the same origin are allowed).
+
+        3. Fix DatabaseTracker::getMaxSizeForDatabase() to check for when
+           disk usage may exceed the quota, and ensure that we will return
+           an appropriate max database size.
+
+           Disk usage can exceed the usage if it is already near the quota limit
+           but have not exceeded it yet. If a new database is opened in that
+           origin, it may bump the usage above the quota, but should not
+           continually repeat this. Subsequent attempts to open a database
+           will find that the quota is already exhausted and fail.
+
+           There is still a race condition pertaining to the tracker database
+           getting out of sync that may still enable runaway growth in the
+           database sizes. That issue only manifest in a multi-process
+           environment, and will be fixed in another changeset.
+
+        4. Fixed a bug in SQLStatement to check if the errorCallback exists
+           before invoking it.
+
+        No new layout tests. A quota-test.html was attached to bugzilla for manual
+        testing of multi-tab concurrent consumption of storage resource, and also
+        to test handling situations when the user deletes the database files while
+        the script is still using the database.
+
+        * CMakeLists.txt:
+        * GNUmakefile.list.am:
+        * Modules/webdatabase/DatabaseTracker.cpp:
+        (WebCore::DatabaseTracker::getMaxSizeForDatabase):
+        (WebCore::DatabaseTracker::originLockFor):
+        (WebCore::DatabaseTracker::deleteOriginLockFor):
+        (WebCore::DatabaseTracker::deleteOrigin):
+        * Modules/webdatabase/DatabaseTracker.h:
+        * Modules/webdatabase/OriginLock.cpp: Added.
+        (WebCore::OriginLock::lockFileNameForPath):
+        (WebCore::OriginLock::OriginLock):
+        (WebCore::OriginLock::~OriginLock):
+        (WebCore::OriginLock::lock):
+        (WebCore::OriginLock::unlock):
+        * Modules/webdatabase/OriginLock.h: Added.
+        * Modules/webdatabase/SQLStatement.cpp:
+        (WebCore::SQLStatement::performCallback):
+        * Modules/webdatabase/SQLTransactionBackend.cpp:
+        (WebCore::SQLTransactionBackend::doCleanup):
+        (WebCore::SQLTransactionBackend::computeNextStateAndCleanupIfNeeded):
+        (WebCore::SQLTransactionBackend::openTransactionAndPreflight):
+        (WebCore::SQLTransactionBackend::postflightAndCommit):
+        (WebCore::SQLTransactionBackend::cleanupAfterTransactionErrorCallback):
+        (WebCore::SQLTransactionBackend::acquireOriginLock):
+        (WebCore::SQLTransactionBackend::releaseOriginLockIfNeeded):
+        * Modules/webdatabase/SQLTransactionBackend.h:
+        (SQLTransactionBackend):
+        * Modules/webdatabase/SQLTransactionCoordinator.cpp:
+        (WebCore::getDatabaseIdentifier):
+        * Target.pri:
+        * WebCore.gypi:
+        * WebCore.vcproj/WebCore.vcproj:
+        * WebCore.vcxproj/WebCore.vcxproj:
+        * WebCore.vcxproj/WebCore.vcxproj.filters:
+        * WebCore.xcodeproj/project.pbxproj:
+        * config.h:
+        * platform/FileSystem.h:
+        * platform/posix/FileSystemPOSIX.cpp:
+        (WebCore::lockFile):
+        (WebCore::unlockFile):
+
 2013-03-05  Ilya Tikhonovsky  <loislo@chromium.org>
 
         Web Inspector: move PopoverContentHelper from TimelinePresentationModel.js to Popover.js.
index 7009766..b266b5d 100644 (file)
@@ -2150,6 +2150,8 @@ webcore_modules_sources += \
        Source/WebCore/Modules/webdatabase/DatabaseThread.h \
        Source/WebCore/Modules/webdatabase/DatabaseTracker.cpp \
        Source/WebCore/Modules/webdatabase/DatabaseTracker.h \
+       Source/WebCore/Modules/webdatabase/OriginLock.cpp \
+       Source/WebCore/Modules/webdatabase/OriginLock.h \
        Source/WebCore/Modules/webdatabase/SQLCallbackWrapper.h \
        Source/WebCore/Modules/webdatabase/SQLError.h \
        Source/WebCore/Modules/webdatabase/SQLException.cpp \
index 30e5fe1..8c21a22 100644 (file)
@@ -40,6 +40,7 @@
 #include "DatabaseThread.h"
 #include "FileSystem.h"
 #include "Logging.h"
+#include "OriginLock.h"
 #include "Page.h"
 #include "SecurityOrigin.h"
 #include "SecurityOriginHash.h"
@@ -282,6 +283,9 @@ unsigned long long DatabaseTracker::getMaxSizeForDatabase(const DatabaseBackendB
     unsigned long long databaseFileSize = SQLiteFileSystem::getDatabaseFileSize(database->fileName());
     ASSERT(databaseFileSize <= diskUsage);
 
+    if (diskUsage > quota)
+        return databaseFileSize;
+
     // A previous error may have allowed the origin to exceed its quota, or may
     // have allowed this database to exceed our cached estimate of the origin
     // disk usage. Don't multiply that error through integer underflow, or the
@@ -648,6 +652,53 @@ void DatabaseTracker::getOpenDatabases(SecurityOrigin* origin, const String& nam
         databases->add(*it);
 }
 
+PassRefPtr<OriginLock> DatabaseTracker::originLockFor(SecurityOrigin* origin)
+{
+    MutexLocker lockDatabase(m_databaseGuard);
+    String databaseIdentifier = origin->databaseIdentifier();
+
+    // The originLockMap is accessed from multiple DatabaseThreads since
+    // different script contexts can be writing to different databases from
+    // the same origin. Hence, the databaseIdentifier key needs to be an
+    // isolated copy. An isolated copy gives us a value whose refCounting is
+    // thread-safe, since our copy is guarded by the m_databaseGuard mutex.
+    databaseIdentifier = databaseIdentifier.isolatedCopy();
+
+    OriginLockMap::AddResult addResult =
+        m_originLockMap.add(databaseIdentifier, RefPtr<OriginLock>());
+    if (!addResult.isNewEntry)
+        return addResult.iterator->value;
+
+    String path = originPath(origin);
+    RefPtr<OriginLock> lock = adoptRef(new OriginLock(path));
+    ASSERT(lock);
+    addResult.iterator->value = lock;
+
+    return lock.release();
+}
+
+void DatabaseTracker::deleteOriginLockFor(SecurityOrigin* origin)
+{
+    ASSERT(!m_databaseGuard.tryLock());
+
+    // There is not always an instance of an OriginLock associated with an origin.
+    // For example, if the OriginLock lock file was created by a previous run of
+    // the browser which has now terminated, and the current browser process
+    // has not executed any database transactions from this origin that would
+    // have created the OriginLock instance in memory. In this case, we will have
+    // a lock file but not an OriginLock instance in memory.
+
+    // This function is only called if we are already deleting all the database
+    // files in this origin. We'll give the OriginLock one chance to do an
+    // orderly clean up first when we remove its ref from the m_originLockMap.
+    // This may or may not be possible depending on whether other threads are
+    // also using the OriginLock at the same time. After that, we will go ahead
+    // and delete the lock file.
+
+    m_originLockMap.remove(origin->databaseIdentifier());
+    OriginLock::deleteLockFile(originPath(origin));
+}
+
 unsigned long long DatabaseTracker::usageForOrigin(SecurityOrigin* origin)
 {
     String originPath = this->originPath(origin);
@@ -798,6 +849,7 @@ bool DatabaseTracker::deleteOrigin(SecurityOrigin* origin)
 
     {
         MutexLocker lockDatabase(m_databaseGuard);
+        deleteOriginLockFor(origin);
         doneDeletingOrigin(origin);
 
         SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
index 1ca6e89..6d2e0d3 100644 (file)
@@ -48,6 +48,7 @@ namespace WebCore {
 
 class DatabaseBackendBase;
 class DatabaseBackendContext;
+class OriginLock;
 class SecurityOrigin;
 
 #if !PLATFORM(CHROMIUM)
@@ -102,6 +103,7 @@ public:
     unsigned long long usageForOrigin(SecurityOrigin*);
     unsigned long long quotaForOrigin(SecurityOrigin*);
     void setQuota(SecurityOrigin*, unsigned long long);
+    PassRefPtr<OriginLock> originLockFor(SecurityOrigin*);
 
     void deleteAllDatabases();
     bool deleteOrigin(SecurityOrigin*);
@@ -139,6 +141,8 @@ private:
 
     bool deleteDatabaseFile(SecurityOrigin*, const String& name);
 
+    void deleteOriginLockFor(SecurityOrigin*);
+
     typedef HashSet<DatabaseBackendBase*> DatabaseSet;
     typedef HashMap<String, DatabaseSet*> DatabaseNameMap;
     typedef HashMap<RefPtr<SecurityOrigin>, DatabaseNameMap*> DatabaseOriginMap;
@@ -153,6 +157,9 @@ private:
     typedef HashMap<RefPtr<SecurityOrigin>, unsigned long long> QuotaMap;
     mutable OwnPtr<QuotaMap> m_quotaMap;
 
+    typedef HashMap<String, RefPtr<OriginLock> > OriginLockMap;
+    OriginLockMap m_originLockMap;
+
     String m_databaseDirectoryPath;
 
     DatabaseManagerClient* m_client;
diff --git a/Source/WebCore/Modules/webdatabase/OriginLock.cpp b/Source/WebCore/Modules/webdatabase/OriginLock.cpp
new file mode 100644 (file)
index 0000000..7361e36
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013 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 "OriginLock.h"
+
+#if ENABLE(SQL_DATABASE)
+
+#include "FileSystem.h"
+#include <wtf/PassOwnPtr.h>
+
+namespace WebCore {
+
+String OriginLock::lockFileNameForPath(String originPath)
+{
+    return pathByAppendingComponent(originPath, String(".lock"));
+}
+
+OriginLock::OriginLock(String originPath)
+    : m_lockFileName(lockFileNameForPath(originPath).isolatedCopy())
+#if USE(FILE_LOCK)
+    , m_lockHandle(invalidPlatformFileHandle)
+#endif
+{
+}
+
+OriginLock::~OriginLock()
+{
+}
+
+void OriginLock::lock()
+{
+    m_mutex.lock();
+
+#if USE(FILE_LOCK)
+    m_lockHandle = openFile(m_lockFileName, OpenForWrite);
+    if (m_lockHandle == invalidPlatformFileHandle) {
+        // The only way we can get here is if the directory containing the lock
+        // has been deleted or we were given a path to a non-existant directory.
+        // In that case, there's nothing we can do but cleanup and return.
+        m_mutex.unlock();
+        return;
+    }
+
+    lockFile(m_lockHandle, LockExclusive);
+#endif
+}
+
+void OriginLock::unlock()
+{
+#if USE(FILE_LOCK)
+    // If the file descriptor was uninitialized, then that means the directory
+    // containing the lock has been deleted before we opened the lock file, or
+    // we were given a path to a non-existant directory. Which, in turn, means
+    // that there's nothing to unlock.
+    if (m_lockHandle == invalidPlatformFileHandle) 
+        return;
+
+    unlockFile(m_lockHandle);
+
+    closeFile(m_lockHandle);
+    m_lockHandle = invalidPlatformFileHandle;
+#endif
+
+    m_mutex.unlock();
+}
+
+void OriginLock::deleteLockFile(String originPath)
+{
+    UNUSED_PARAM(originPath);
+#if USE(FILE_LOCK)
+    String lockFileName = OriginLock::lockFileNameForPath(originPath);
+    deleteFile(lockFileName);
+#endif
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(SQL_DATABASE)
diff --git a/Source/WebCore/Modules/webdatabase/OriginLock.h b/Source/WebCore/Modules/webdatabase/OriginLock.h
new file mode 100644 (file)
index 0000000..543eb06
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 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. 
+ */
+
+#ifndef OriginLock_h
+#define OriginLock_h
+
+#if ENABLE(SQL_DATABASE)
+
+#include "FileSystem.h"
+#include <wtf/ThreadSafeRefCounted.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class OriginLock : public ThreadSafeRefCounted<OriginLock> {
+    WTF_MAKE_NONCOPYABLE(OriginLock); WTF_MAKE_FAST_ALLOCATED;
+public:
+    OriginLock(String originPath);
+    ~OriginLock();
+
+    void lock();
+    void unlock();
+
+    static void deleteLockFile(String originPath);
+
+private:
+    static String lockFileNameForPath(String originPath);
+
+    String m_lockFileName;
+    Mutex m_mutex;
+#if USE(FILE_LOCK)
+    PlatformFileHandle m_lockHandle;
+#endif
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(SQL_DATABASE)
+
+#endif // OriginLock_h
index 733143b..92c7f4f 100644 (file)
@@ -87,8 +87,8 @@ bool SQLStatement::performCallback(SQLTransaction* transaction)
     // Call the appropriate statement callback and track if it resulted in an error,
     // because then we need to jump to the transaction error callback.
     if (error) {
-        ASSERT(errorCallback);
-        callbackError = errorCallback->handleEvent(transaction, error.get());
+        if (errorCallback)
+            callbackError = errorCallback->handleEvent(transaction, error.get());
     } else if (callback) {
         RefPtr<SQLResultSet> resultSet = m_backend->sqlResultSet();
         callbackError = !callback->handleEvent(transaction, resultSet.get());
index a3e7df8..bb548ba 100644 (file)
 #include "DatabaseBackend.h"
 #include "DatabaseBackendContext.h"
 #include "DatabaseThread.h"
+#include "DatabaseTracker.h"
 #include "ExceptionCode.h"
 #include "Logging.h"
+#include "OriginLock.h"
 #include "SQLError.h"
 #include "SQLStatementBackend.h"
 #include "SQLTransactionClient.h"
@@ -386,6 +388,8 @@ void SQLTransactionBackend::doCleanup()
 
     ASSERT(currentThread() == database()->databaseContext()->databaseThread()->getThreadID());
 
+    releaseOriginLockIfNeeded();
+
     MutexLocker locker(m_statementMutex);
     m_statementQueue.clear();
 
@@ -491,6 +495,7 @@ void SQLTransactionBackend::computeNextStateAndCleanupIfNeeded()
         return;
     }
 
+    // If we get here, then we should be shutting down. Do clean up if needed:
     if (m_nextState == SQLTransactionState::End)
         return;
     m_nextState = SQLTransactionState::End;
@@ -570,8 +575,10 @@ SQLTransactionState SQLTransactionBackend::openTransactionAndPreflight()
     }
 
     // Set the maximum usage for this transaction if this transactions is not read-only
-    if (!m_readOnly)
+    if (!m_readOnly) {
+        acquireOriginLock();
         m_database->sqliteDatabase().setMaximumSize(m_database->maximumSize());
+    }
 
     ASSERT(!m_sqliteTransaction);
     m_sqliteTransaction = adoptPtr(new SQLiteTransaction(m_database->sqliteDatabase(), m_readOnly));
@@ -740,6 +747,8 @@ SQLTransactionState SQLTransactionBackend::postflightAndCommit()
     m_sqliteTransaction->commit();
     m_database->enableAuthorizer();
 
+    releaseOriginLockIfNeeded();
+
     // If the commit failed, the transaction will still be marked as "in progress"
     if (m_sqliteTransaction->inProgress()) {
         if (m_wrapper)
@@ -804,6 +813,8 @@ SQLTransactionState SQLTransactionBackend::cleanupAfterTransactionErrorCallback(
     }
     m_database->enableAuthorizer();
 
+    releaseOriginLockIfNeeded();
+
     ASSERT(!m_database->sqliteDatabase().transactionInProgress());
 
     return SQLTransactionState::CleanupAndTerminate;
@@ -836,6 +847,25 @@ SQLTransactionState SQLTransactionBackend::sendToFrontendState()
     return SQLTransactionState::Idle;
 }
 
+void SQLTransactionBackend::acquireOriginLock()
+{
+#if !PLATFORM(CHROMIUM)
+    ASSERT(!m_originLock);
+    m_originLock = DatabaseTracker::tracker().originLockFor(m_database->securityOrigin());
+    m_originLock->lock();
+#endif
+}
+
+void SQLTransactionBackend::releaseOriginLockIfNeeded()
+{
+#if !PLATFORM(CHROMIUM)
+    if (m_originLock) {
+        m_originLock->unlock();
+        m_originLock.clear();
+    }
+#endif
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(SQL_DATABASE)
index ce5459f..00c2280 100644 (file)
@@ -42,6 +42,7 @@ namespace WebCore {
 
 class AbstractSQLTransaction;
 class DatabaseBackend;
+class OriginLock;
 class SQLError;
 class SQLiteTransaction;
 class SQLStatementBackend;
@@ -108,6 +109,9 @@ private:
 
     void getNextStatement();
 
+    void acquireOriginLock();
+    void releaseOriginLockIfNeeded();
+
     RefPtr<AbstractSQLTransaction> m_frontend; // Has a reference cycle, and will break in doCleanup().
     RefPtr<SQLStatementBackend> m_currentStatementBackend;
 
@@ -128,6 +132,9 @@ private:
     Deque<RefPtr<SQLStatementBackend> > m_statementQueue;
 
     OwnPtr<SQLiteTransaction> m_sqliteTransaction;
+#if !PLATFORM(CHROMIUM)
+    RefPtr<OriginLock> m_originLock;
+#endif
 };
 
 } // namespace WebCore
index 295c24d..63c30fa 100644 (file)
@@ -36,6 +36,7 @@
 
 #include "DatabaseBackend.h"
 #include "SQLTransactionBackend.h"
+#include "SecurityOrigin.h"
 #include <wtf/Deque.h>
 #include <wtf/HashMap.h>
 #include <wtf/HashSet.h>
@@ -47,7 +48,7 @@ static String getDatabaseIdentifier(SQLTransactionBackend* transaction)
 {
     DatabaseBackend* database = transaction->database();
     ASSERT(database);
-    return database->stringIdentifier();
+    return database->securityOrigin()->databaseIdentifier();
 }
 
 SQLTransactionCoordinator::SQLTransactionCoordinator()
index 4a9764a..0a90142 100644 (file)
@@ -1479,6 +1479,7 @@ HEADERS += \
     Modules/webdatabase/DatabaseTask.h \
     Modules/webdatabase/DatabaseThread.h \
     Modules/webdatabase/DatabaseTracker.h \
+    Modules/webdatabase/OriginLock.h \
     Modules/webdatabase/SQLCallbackWrapper.h \
     Modules/webdatabase/SQLResultSet.h \
     Modules/webdatabase/SQLResultSetRowList.h \
@@ -3070,6 +3071,7 @@ enable?(SQL_DATABASE) {
         Modules/webdatabase/DatabaseTask.cpp \
         Modules/webdatabase/DatabaseThread.cpp \
         Modules/webdatabase/DatabaseTracker.cpp \
+        Modules/webdatabase/OriginLock.cpp \
         Modules/webdatabase/SQLException.cpp \
         Modules/webdatabase/SQLResultSet.cpp \
         Modules/webdatabase/SQLResultSetRowList.cpp \
index 7cd5915..bc8fe84 100644 (file)
         ['exclude', 'Modules/indexeddb/IDBFactoryBackendInterface\\.cpp$'],
         ['exclude', 'Modules/webdatabase/DatabaseManagerClient\\.h$'],
         ['exclude', 'Modules/webdatabase/DatabaseTracker\\.cpp$'],
+        ['exclude', 'Modules/webdatabase/OriginLock\\.cpp$'],
         ['exclude', 'Modules/webdatabase/SQLTransactionClient\\.cpp$'],
         ['exclude', 'inspector/InspectorFrontendClientLocal\\.cpp$'],
         ['exclude', 'inspector/JavaScript[^/]*\\.cpp$'],
index ec84633..f0ef9fa 100644 (file)
             'Modules/webdatabase/DatabaseThread.cpp',
             'Modules/webdatabase/DatabaseThread.h',
             'Modules/webdatabase/DatabaseTracker.cpp',
+            'Modules/webdatabase/DatabaseTracker.h',
             'Modules/webdatabase/DOMWindowWebDatabase.cpp',
             'Modules/webdatabase/DOMWindowWebDatabase.h',
+            'Modules/webdatabase/OriginLock.cpp',
+            'Modules/webdatabase/OriginLock.h',
             'Modules/webdatabase/SQLCallbackWrapper.h',
             'Modules/webdatabase/SQLException.cpp',
             'Modules/webdatabase/SQLException.h',
index 3c77405..7ed69e8 100755 (executable)
                                        >
                                </File>
                                <File
+                                       RelativePath="..\Modules\webdatabase\OriginLock.cpp"
+                                       >
+                               </File>
+                               <File
+                                       RelativePath="..\Modules\webdatabase\OriginLock.h"
+                                       >
+                               </File>
+                               <File
                                        RelativePath="..\Modules\webdatabase\SQLCallbackWrapper.h"
                                        >
                                </File>
index fa3d25b..684a01c 100644 (file)
     <ClCompile Include="..\Modules\webdatabase\DatabaseThread.cpp" />
     <ClCompile Include="..\Modules\webdatabase\DatabaseTracker.cpp" />
     <ClCompile Include="..\Modules\webdatabase\DOMWindowWebDatabase.cpp" />
+    <ClCompile Include="..\Modules\webdatabase\OriginLock.cpp" />
     <ClCompile Include="..\Modules\webdatabase\SQLException.cpp" />
     <ClCompile Include="..\Modules\webdatabase\SQLResultSet.cpp" />
     <ClCompile Include="..\Modules\webdatabase\SQLResultSetRowList.cpp" />
     <ClInclude Include="..\Modules\webdatabase\DatabaseThread.h" />
     <ClInclude Include="..\Modules\webdatabase\DatabaseTracker.h" />
     <ClInclude Include="..\Modules\webdatabase\DOMWindowWebDatabase.h" />
+    <ClInclude Include="..\Modules\webdatabase\OriginLock.h" />
     <ClInclude Include="..\Modules\webdatabase\SQLCallbackWrapper.h" />
     <ClInclude Include="..\Modules\webdatabase\SQLError.h" />
     <ClInclude Include="..\Modules\webdatabase\SQLException.h" />
index 049b7bc..50ca63d 100644 (file)
     <ClCompile Include="..\Modules\webdatabase\DOMWindowWebDatabase.cpp">
       <Filter>Modules\webdatabase</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\webdatabase\OriginLock.cpp">
+      <Filter>Modules\webdatabase</Filter>
+    </ClCompile>
     <ClCompile Include="..\Modules\webdatabase\SQLException.cpp">
       <Filter>Modules\webdatabase</Filter>
     </ClCompile>
     <ClInclude Include="..\Modules\webdatabase\DOMWindowWebDatabase.h">
       <Filter>Modules\webdatabase</Filter>
     </ClInclude>
+    <ClInclude Include="..\Modules\webdatabase\OriginLock.h">
+      <Filter>Modules\webdatabase</Filter>
+    </ClInclude>
     <ClInclude Include="..\Modules\webdatabase\SQLCallbackWrapper.h">
       <Filter>Modules\webdatabase</Filter>
     </ClInclude>
index 0fb91a0..ca2811c 100644 (file)
                FE80DA720E9C472F000D6F75 /* JSPositionError.h in Headers */ = {isa = PBXBuildFile; fileRef = FE80DA6E0E9C472F000D6F75 /* JSPositionError.h */; };
                FE8A674716CDD19E00930BF8 /* SQLStatementBackend.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FE8A674516CDD19E00930BF8 /* SQLStatementBackend.cpp */; };
                FE8A674816CDD19E00930BF8 /* SQLStatementBackend.h in Headers */ = {isa = PBXBuildFile; fileRef = FE8A674616CDD19E00930BF8 /* SQLStatementBackend.h */; };
+               FE9E89FB16E2DC0500A908F8 /* OriginLock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FE9E89F916E2DC0400A908F8 /* OriginLock.cpp */; };
+               FE9E89FC16E2DC0500A908F8 /* OriginLock.h in Headers */ = {isa = PBXBuildFile; fileRef = FE9E89FA16E2DC0400A908F8 /* OriginLock.h */; settings = {ATTRIBUTES = (Private, ); }; };
                FEAD7D8716C339EE00D4670B /* SQLTransactionBackendSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FEAD7D8516C339EE00D4670B /* SQLTransactionBackendSync.cpp */; };
                FEAD7D8816C339EE00D4670B /* SQLTransactionBackendSync.h in Headers */ = {isa = PBXBuildFile; fileRef = FEAD7D8616C339EE00D4670B /* SQLTransactionBackendSync.h */; };
                FEAF6654167970320062D0C5 /* DatabaseServer.h in Headers */ = {isa = PBXBuildFile; fileRef = FEAF6653167970070062D0C5 /* DatabaseServer.h */; settings = {ATTRIBUTES = (Private, ); }; };
                FE80DA6E0E9C472F000D6F75 /* JSPositionError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSPositionError.h; sourceTree = "<group>"; };
                FE8A674516CDD19E00930BF8 /* SQLStatementBackend.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SQLStatementBackend.cpp; path = Modules/webdatabase/SQLStatementBackend.cpp; sourceTree = "<group>"; };
                FE8A674616CDD19E00930BF8 /* SQLStatementBackend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLStatementBackend.h; path = Modules/webdatabase/SQLStatementBackend.h; sourceTree = "<group>"; };
+               FE9E89F916E2DC0400A908F8 /* OriginLock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OriginLock.cpp; path = Modules/webdatabase/OriginLock.cpp; sourceTree = "<group>"; };
+               FE9E89FA16E2DC0400A908F8 /* OriginLock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OriginLock.h; path = Modules/webdatabase/OriginLock.h; sourceTree = "<group>"; };
                FEAD7D8516C339EE00D4670B /* SQLTransactionBackendSync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SQLTransactionBackendSync.cpp; path = Modules/webdatabase/SQLTransactionBackendSync.cpp; sourceTree = "<group>"; };
                FEAD7D8616C339EE00D4670B /* SQLTransactionBackendSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLTransactionBackendSync.h; path = Modules/webdatabase/SQLTransactionBackendSync.h; sourceTree = "<group>"; };
                FEAF6653167970070062D0C5 /* DatabaseServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DatabaseServer.h; path = Modules/webdatabase/DatabaseServer.h; sourceTree = "<group>"; };
                                A8CCBB46151F831600AB7CE9 /* DOMWindowWebDatabase.cpp */,
                                A8CCBB47151F831600AB7CE9 /* DOMWindowWebDatabase.h */,
                                A8CCBB4E151F835A00AB7CE9 /* DOMWindowWebDatabase.idl */,
+                               FE9E89F916E2DC0400A908F8 /* OriginLock.cpp */,
+                               FE9E89FA16E2DC0400A908F8 /* OriginLock.h */,
                                97BC69F91505F081001B74AC /* SQLCallbackWrapper.h */,
                                97BC69FA1505F081001B74AC /* SQLError.h */,
                                97BC69FB1505F081001B74AC /* SQLError.idl */,
                        buildActionMask = 2147483647;
                        files = (
                                FE115FAB167988CD00249134 /* AbstractDatabaseServer.h in Headers */,
+                               FE9E89FC16E2DC0500A908F8 /* OriginLock.h in Headers */,
                                FE4AADEE16D2C37400026FFC /* AbstractSQLStatement.h in Headers */,
                                FE4AADEF16D2C37400026FFC /* AbstractSQLStatementBackend.h in Headers */,
                                41E1B1D10FF5986900576B3B /* AbstractWorker.h in Headers */,
                                FDB8D1E516B0CC9700340F10 /* ExclusionShapeInfo.cpp in Sources */,
                                FD748ABF15BF74ED0059CF0D /* ExclusionShapeInsideInfo.cpp in Sources */,
                                9A9CEF8D163B3EA100DE7EFE /* ExclusionShapeOutsideInfo.cpp in Sources */,
+                               FE9E89FB16E2DC0500A908F8 /* OriginLock.cpp in Sources */,
                                6E67D2A61280E8A4008758F7 /* Extensions3DOpenGL.cpp in Sources */,
                                44DAB5B115A623580097C1E4 /* Extensions3DOpenGLCommon.cpp in Sources */,
                                7728694E14F8882500F484DC /* EXTTextureFilterAnisotropic.cpp in Sources */,
index df6ff92..a2c94a0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2004, 2005, 2006 Apple Inc.
+ * Copyright (C) 2004, 2005, 2006, 2013 Apple Inc.
  * Copyright (C) 2009 Google Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
 
 #include <wtf/Platform.h>
 
+#if PLATFORM(MAC) || PLATFORM(IOS)
+#define WTF_USE_FILE_LOCK 1
+#endif
+
 #if PLATFORM(WIN) && !OS(WINCE)
 #include <WebCore/WebCoreHeaderDetection.h>
 #endif
index 71bf327..a2b5b4c 100644 (file)
@@ -152,6 +152,12 @@ enum FileSeekOrigin {
     SeekFromEnd
 };
 
+enum FileLockMode {
+    LockShared = 1,
+    LockExclusive = 2,
+    LockNonBlocking = 4
+};
+
 #if OS(WINDOWS)
 static const char PlatformFilePathSeparator = '\\';
 #else
@@ -195,6 +201,10 @@ bool truncateFile(PlatformFileHandle, long long offset);
 int writeToFile(PlatformFileHandle, const char* data, int length);
 // Returns number of bytes actually written if successful, -1 otherwise.
 int readFromFile(PlatformFileHandle, char* data, int length);
+#if USE(FILE_LOCK)
+bool lockFile(PlatformFileHandle, FileLockMode);
+bool unlockFile(PlatformFileHandle);
+#endif
 
 // Functions for working with loadable modules.
 bool unloadModule(PlatformModule);
index 8acc0b0..cf7d75f 100644 (file)
@@ -139,6 +139,23 @@ int readFromFile(PlatformFileHandle handle, char* data, int length)
     return -1;
 }
 
+#if USE(FILE_LOCK)
+bool lockFile(PlatformFileHandle handle, FileLockMode lockMode)
+{
+    COMPILE_ASSERT(LOCK_SH == LockShared, LockSharedEncodingIsAsExpected);
+    COMPILE_ASSERT(LOCK_EX == LockExclusive, LockExclusiveEncodingIsAsExpected);
+    COMPILE_ASSERT(LOCK_NB == LockNonBlocking, LockNonBlockingEncodingIsAsExpected);
+    int result = flock(handle, lockMode);
+    return (result != -1);
+}
+
+bool unlockFile(PlatformFileHandle handle)
+{
+    int result = flock(handle, LOCK_UN);
+    return (result != -1);
+}
+#endif
+
 bool deleteEmptyDirectory(const String& path)
 {
     CString fsRep = fileSystemRepresentation(path);