Bug 571599 - Use sqlite3_unlock_notify
authorShawn Wilsher <sdwilsh@shawnwilsher.com>
Mon, 21 Jun 2010 14:23:00 -0700
changeset 44141 bf063aaac85c07487717df167afe6db018d0d5c2
parent 44140 ca87ba56dd6e4eec9bb6294ffd220f98a0acbe56
child 44142 21c3e7e2403352810e684d38fe8f760cfdb50270
push idunknown
push userunknown
push dateunknown
bugs571599
milestone1.9.3a6pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 571599 - Use sqlite3_unlock_notify sr=vlad r=bent r=asuth
configure.in
db/sqlite3/src/Makefile.in
db/sqlite3/src/sqlite.def
storage/src/mozStorageAsyncStatement.cpp
storage/src/mozStorageAsyncStatementExecution.cpp
storage/src/mozStorageConnection.cpp
storage/src/mozStoragePrivateHelpers.cpp
storage/src/mozStoragePrivateHelpers.h
storage/src/mozStorageStatement.cpp
storage/test/Makefile.in
storage/test/storage_test_harness.h
storage/test/test_unlock_notify.cpp
--- a/configure.in
+++ b/configure.in
@@ -6698,17 +6698,17 @@ else
     if test "x$ac_cv_sqlite_threadsafe" = "xno"; then
         AC_MSG_ERROR([System SQLite library is not compiled with SQLITE_THREADSAFE.])
     fi
 
     dnl ================================
     dnl === SQLITE_ENABLE_FTS3 check ===
     dnl ================================
     dnl check to see if the system SQLite package is compiled with
-    dnl SQLITE_THREADSAFE enabled.
+    dnl SQLITE_ENABLE_FTS3 enabled.
     AC_MSG_CHECKING(for SQLITE_ENABLE_FTS3 support in system SQLite)
     _SAVE_CFLAGS="$CFLAGS"
     CFLAGS="$CFLAGS $SQLITE_CFLAGS"
     _SAVE_LIBS="$LIBS"
     LIBS="$LIBS $SQLITE_LIBS"
     AC_CACHE_VAL(ac_cv_sqlite_enable_fts3,[
         AC_TRY_RUN([
             #include "sqlite3.h"
@@ -6722,16 +6722,45 @@ else
         )
     ])
     AC_MSG_RESULT($ac_cv_sqlite_enable_fts3)
     CFLAGS="$_SAVE_CFLAGS"
     LIBS="$_SAVE_LIBS"
     if test "x$ac_cv_sqlite_enable_fts3" = "xno"; then
         AC_MSG_ERROR([System SQLite library is not compiled with SQLITE_ENABLE_FTS3.])
     fi
+
+    dnl =========================================
+    dnl === SQLITE_ENABLE_UNLOCK_NOTIFY check ===
+    dnl =========================================
+    dnl check to see if the system SQLite package is compiled with
+    dnl SQLITE_ENABLE_UNLOCK_NOTIFY enabled.
+    AC_MSG_CHECKING(for SQLITE_ENABLE_UNLOCK_NOTIFY support in system SQLite)
+    _SAVE_CFLAGS="$CFLAGS"
+    CFLAGS="$CFLAGS $SQLITE_CFLAGS"
+    _SAVE_LIBS="$LIBS"
+    LIBS="$LIBS $SQLITE_LIBS"
+    AC_CACHE_VAL(ac_cv_sqlite_enable_unlock_notify,[
+        AC_TRY_RUN([
+            #include "sqlite3.h"
+
+            int main(int argc, char **argv){
+              return !sqlite3_compileoption_used("SQLITE_ENABLE_UNLOCK_NOTIFY");
+            }],
+            ac_cv_sqlite_enable_unlock_notify=yes,
+            ac_cv_sqlite_enable_unlock_notify=no,
+            ac_cv_sqlite_enable_unlock_notify=no
+        )
+    ])
+    AC_MSG_RESULT($ac_cv_sqlite_enable_unlock_notify)
+    CFLAGS="$_SAVE_CFLAGS"
+    LIBS="$_SAVE_LIBS"
+    if test "x$ac_cv_sqlite_enable_unlock_notify" = "xno"; then
+        AC_MSG_ERROR([System SQLite library is not compiled with SQLITE_ENABLE_UNLOCK_NOTIFY.])
+    fi
 fi
 
 AC_SUBST(MOZ_NATIVE_SQLITE)
 
 dnl ========================================================
 dnl = Enable help viewer (off by default)
 dnl ========================================================
 if test -n "$MOZ_HELP_VIEWER"; then
--- a/db/sqlite3/src/Makefile.in
+++ b/db/sqlite3/src/Makefile.in
@@ -101,16 +101,17 @@ CSRCS = \
 # -DSQLITE_ENABLE_FTS3=1 enables the full-text index module.
 # -DSQLITE_CORE=1 statically links that module into the SQLite library.
 # Note: Be sure to update the configure.in checks when these change!
 DEFINES = \
   -DSQLITE_SECURE_DELETE=1 \
   -DSQLITE_THREADSAFE=1 \
   -DSQLITE_CORE=1 \
   -DSQLITE_ENABLE_FTS3=1 \
+  -DSQLITE_ENABLE_UNLOCK_NOTIFY=1 \
   $(NULL)
 
 # -DSQLITE_ENABLE_LOCKING_STYLE=1 to help with AFP folders
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 DEFINES += -DSQLITE_ENABLE_LOCKING_STYLE=1
 endif
 
 # Turn on SQLite's assertions in debug builds.
--- a/db/sqlite3/src/sqlite.def
+++ b/db/sqlite3/src/sqlite.def
@@ -153,16 +153,17 @@ EXPORTS
         sqlite3_snprintf
         sqlite3_sql
         sqlite3_step
         sqlite3_stmt_status
         sqlite3_thread_cleanup
         sqlite3_total_changes
         sqlite3_trace
         sqlite3_transfer_bindings
+        sqlite3_unlock_notify
         sqlite3_update_hook
         sqlite3_user_data
         sqlite3_value_blob
         sqlite3_value_bytes
         sqlite3_value_bytes16
         sqlite3_value_double
         sqlite3_value_int
         sqlite3_value_int64
--- a/storage/src/mozStorageAsyncStatement.cpp
+++ b/storage/src/mozStorageAsyncStatement.cpp
@@ -327,19 +327,18 @@ AsyncStatement::getAsyncStatement(sqlite
   // Make sure we are never called on the connection's owning thread.
   PRBool onOpenedThread = PR_FALSE;
   (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
   NS_ASSERTION(!onOpenedThread,
                "We should only be called on the async thread!");
 #endif
 
   if (!mAsyncStatement) {
-    int rc = ::sqlite3_prepare_v2(mDBConnection->GetNativeConnection(),
-                                  mSQLString.get(), -1,
-                                  &mAsyncStatement, NULL);
+    int rc = prepareStmt(mDBConnection->GetNativeConnection(), mSQLString,
+                         &mAsyncStatement);
     if (rc != SQLITE_OK) {
 #ifdef PR_LOGGING
       PR_LOG(gStorageLog, PR_LOG_ERROR,
              ("Sqlite statement prepare error: %d '%s'", rc,
               ::sqlite3_errmsg(mDBConnection->GetNativeConnection())));
       PR_LOG(gStorageLog, PR_LOG_ERROR,
              ("Statement was: '%s'", mSQLString.get()));
 #endif
--- a/storage/src/mozStorageAsyncStatementExecution.cpp
+++ b/storage/src/mozStorageAsyncStatementExecution.cpp
@@ -337,17 +337,17 @@ bool
 AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
 {
   mMutex.AssertNotCurrentThreadOwns();
 
   while (true) {
     // lock the sqlite mutex so sqlite3_errmsg cannot change
     SQLiteMutexAutoLock lockedScope(mDBMutex);
 
-    int rc = ::sqlite3_step(aStatement);
+    int rc = stepStmt(aStatement);
     // Stop if we have no more results.
     if (rc == SQLITE_DONE)
       return false;
 
     // If we got results, we can return now.
     if (rc == SQLITE_ROW)
       return true;
 
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -408,20 +408,20 @@ Connection::initialize(nsIFile *aDatabas
     ::sqlite3_close(mDBConn);
     mDBConn = nsnull;
     return convertResultCode(srv);
   }
 
   // Execute a dummy statement to force the db open, and to verify if it is
   // valid or not.
   sqlite3_stmt *stmt;
-  srv = ::sqlite3_prepare_v2(mDBConn, "SELECT * FROM sqlite_master", -1, &stmt,
-                             NULL);
+  srv = prepareStmt(mDBConn, NS_LITERAL_CSTRING("SELECT * FROM sqlite_master"),
+                    &stmt);
   if (srv == SQLITE_OK) {
-    srv = ::sqlite3_step(stmt);
+    srv = stepStmt(stmt);
 
     if (srv == SQLITE_DONE || srv == SQLITE_ROW)
         srv = SQLITE_OK;
     ::sqlite3_finalize(stmt);
   }
 
   if (srv != SQLITE_OK) {
     ::sqlite3_close(mDBConn);
@@ -471,21 +471,21 @@ Connection::databaseElementExists(enum D
       query.Append("table");
       break;
   }
   query.Append("' AND name ='");
   query.Append(aElementName);
   query.Append("'");
 
   sqlite3_stmt *stmt;
-  int srv = ::sqlite3_prepare_v2(mDBConn, query.get(), -1, &stmt, NULL);
+  int srv = prepareStmt(mDBConn, query, &stmt);
   if (srv != SQLITE_OK)
     return convertResultCode(srv);
 
-  srv = ::sqlite3_step(stmt);
+  srv = stepStmt(stmt);
   // we just care about the return value from step
   (void)::sqlite3_finalize(stmt);
 
   if (srv == SQLITE_ROW) {
     *_exists = PR_TRUE;
     return NS_OK;
   }
   if (srv == SQLITE_DONE) {
--- a/storage/src/mozStoragePrivateHelpers.cpp
+++ b/storage/src/mozStoragePrivateHelpers.cpp
@@ -41,31 +41,36 @@
 #include "sqlite3.h"
 
 #include "jsapi.h"
 #include "jsdate.h"
 
 #include "nsPrintfCString.h"
 #include "nsString.h"
 #include "nsError.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
 #include "nsThreadUtils.h"
 
 #include "Variant.h"
 #include "mozStoragePrivateHelpers.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageCompletionCallback.h"
 #include "mozIStorageBindingParams.h"
 
 namespace mozilla {
 namespace storage {
 
 nsresult
 convertResultCode(int aSQLiteResultCode)
 {
-  switch (aSQLiteResultCode) {
+  // Drop off the extended result bits of the result code.
+  int rc = aSQLiteResultCode & 0xFF;
+
+  switch (rc) {
     case SQLITE_OK:
     case SQLITE_ROW:
     case SQLITE_DONE:
       return NS_OK;
     case SQLITE_CORRUPT:
     case SQLITE_NOTADB:
       return NS_ERROR_FILE_CORRUPTED;
     case SQLITE_PERM:
@@ -92,17 +97,17 @@ convertResultCode(int aSQLiteResultCode)
     case SQLITE_CONSTRAINT:
       return NS_ERROR_STORAGE_CONSTRAINT;
   }
 
   // generic error
 #ifdef DEBUG
   nsCAutoString message;
   message.AppendLiteral("SQLite returned error code ");
-  message.AppendInt(aSQLiteResultCode);
+  message.AppendInt(rc);
   message.AppendLiteral(" , Storage will convert it to NS_ERROR_FAILURE");
   NS_WARNING(message.get());
 #endif
   return NS_ERROR_FAILURE;
 }
 
 void
 checkAndLogStatementPerformance(sqlite3_stmt *aStatement)
@@ -198,10 +203,133 @@ private:
 already_AddRefed<nsIRunnable>
 newCompletionEvent(mozIStorageCompletionCallback *aCallback)
 {
   NS_ASSERTION(aCallback, "Passing a null callback is a no-no!");
   nsCOMPtr<nsIRunnable> event = new CallbackEvent(aCallback);
   return event.forget();
 }
 
+/**
+ * This code is heavily based on the sample at:
+ *   http://www.sqlite.org/unlock_notify.html
+ */
+namespace {
+
+class UnlockNotification
+{
+public:
+  UnlockNotification()
+  : mMutex("UnlockNotification mMutex")
+  , mCondVar(mMutex, "UnlockNotification condVar")
+  , mSignaled(false)
+  {
+  }
+
+  void Wait()
+  {
+    mozilla::MutexAutoLock lock(mMutex);
+    while (!mSignaled) {
+      (void)mCondVar.Wait();
+    }
+  }
+
+  void Signal()
+  {
+    mozilla::MutexAutoLock lock(mMutex);
+    mSignaled = true;
+    (void)mCondVar.Notify();
+  }
+
+private:
+  mozilla::Mutex mMutex;
+  mozilla::CondVar mCondVar;
+  bool mSignaled;
+};
+
+void
+UnlockNotifyCallback(void **aArgs,
+                     int aArgsSize)
+{
+  for (int i = 0; i < aArgsSize; i++) {
+    UnlockNotification *notification =
+      static_cast<UnlockNotification *>(aArgs[i]);
+    notification->Signal();
+  }
+}
+
+int
+WaitForUnlockNotify(sqlite3* aDatabase)
+{
+  UnlockNotification notification;
+  int srv = ::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback,
+                                    &notification);
+  NS_ASSERTION(srv == SQLITE_LOCKED || srv == SQLITE_OK, "Bad result!");
+  if (srv == SQLITE_OK)
+    notification.Wait();
+
+  return srv;
+}
+
+} // anonymous namespace
+
+int
+stepStmt(sqlite3_stmt* aStatement)
+{
+  bool checkedMainThread = false;
+
+  sqlite3* db = ::sqlite3_db_handle(aStatement);
+  (void)::sqlite3_extended_result_codes(db, 1);
+
+  int srv;
+  while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) {
+    if (!checkedMainThread) {
+      checkedMainThread = true;
+      if (NS_IsMainThread()) {
+        NS_WARNING("We won't allow blocking on the main thread!");
+        break;
+      }
+    }
+
+    srv = WaitForUnlockNotify(sqlite3_db_handle(aStatement));
+    if (srv != SQLITE_OK)
+      break;
+
+    ::sqlite3_reset(aStatement);
+  }
+
+  (void)::sqlite3_extended_result_codes(db, 0);
+  // Drop off the extended result bits of the result code.
+  return srv & 0xFF;
+}
+
+int
+prepareStmt(sqlite3* aDatabase,
+            const nsCString &aSQL,
+            sqlite3_stmt **_stmt)
+{
+  bool checkedMainThread = false;
+
+  (void)::sqlite3_extended_result_codes(aDatabase, 1);
+
+  int srv;
+  while((srv = ::sqlite3_prepare_v2(aDatabase, aSQL.get(), -1, _stmt, NULL)) ==
+        SQLITE_LOCKED_SHAREDCACHE) {
+    if (!checkedMainThread) {
+      checkedMainThread = true;
+      if (NS_IsMainThread()) {
+        NS_WARNING("We won't allow blocking on the main thread!");
+        break;
+      }
+    }
+
+    srv = WaitForUnlockNotify(aDatabase);
+    if (srv != SQLITE_OK)
+      break;
+  }
+
+  (void)::sqlite3_extended_result_codes(aDatabase, 0);
+  // Drop off the extended result bits of the result code.
+  return srv & 0xFF;
+}
+
 } // namespace storage
 } // namespace mozilla
--- a/storage/src/mozStoragePrivateHelpers.h
+++ b/storage/src/mozStoragePrivateHelpers.h
@@ -108,12 +108,35 @@ nsIVariant *convertJSValToVariant(JSCont
  * @param aCallback
  *        The callback to be notified.
  * @return an nsIRunnable that can be dispatched to the calling thread.
  */
 already_AddRefed<nsIRunnable> newCompletionEvent(
   mozIStorageCompletionCallback *aCallback
 );
 
+
+/**
+ * Performs a sqlite3_step on aStatement, while properly handling SQLITE_LOCKED
+ * when not on the main thread by waiting until we are notified.
+ *
+ * @param aStatement
+ *        A pointer to a sqlite3_stmt object.
+ * @return the result from sqlite3_step.
+ */
+int stepStmt(sqlite3_stmt *aStatement);
+
+/**
+ * Obtains a prepared sqlite3_stmt object for aDatabase from aSQL.
+ *
+ * @param aDatabase
+ *        The database the statement will execute on.
+ * @param aSQL
+ *        The SQL statement to compile.
+ * @return the result from sqlite3_prepare_v2.
+ */
+int prepareStmt(sqlite3 *aDatabase, const nsCString &aSQL,
+                sqlite3_stmt **_stmt);
+
 } // namespace storage
 } // namespace mozilla
 
 #endif // mozStoragePrivateHelpers_h
--- a/storage/src/mozStorageStatement.cpp
+++ b/storage/src/mozStorageStatement.cpp
@@ -170,18 +170,17 @@ Statement::initialize(Connection *aDBCon
                       const nsACString &aSQLStatement)
 {
   NS_ASSERTION(aDBConnection, "No database connection given!");
   NS_ASSERTION(!mDBStatement, "Statement already initialized!");
 
   sqlite3 *db = aDBConnection->GetNativeConnection();
   NS_ASSERTION(db, "We should never be called with a null sqlite3 database!");
 
-  int srv = ::sqlite3_prepare_v2(db, PromiseFlatCString(aSQLStatement).get(),
-                                 -1, &mDBStatement, NULL);
+  int srv = prepareStmt(db, PromiseFlatCString(aSQLStatement), &mDBStatement);
   if (srv != SQLITE_OK) {
 #ifdef PR_LOGGING
       PR_LOG(gStorageLog, PR_LOG_ERROR,
              ("Sqlite statement prepare error: %d '%s'", srv,
               ::sqlite3_errmsg(db)));
       PR_LOG(gStorageLog, PR_LOG_ERROR,
              ("Statement was: '%s'", PromiseFlatCString(aSQLStatement).get()));
 #endif
@@ -314,19 +313,19 @@ Statement::getOwner()
 int
 Statement::getAsyncStatement(sqlite3_stmt **_stmt)
 {
   // If we have no statement, we shouldn't be calling this method!
   NS_ASSERTION(mDBStatement != NULL, "We have no statement to clone!");
 
   // If we do not yet have a cached async statement, clone our statement now.
   if (!mAsyncStatement) {
-    int rc = ::sqlite3_prepare_v2(mDBConnection->GetNativeConnection(),
-                                  ::sqlite3_sql(mDBStatement), -1,
-                                  &mAsyncStatement, NULL);
+    nsDependentCString sql(::sqlite3_sql(mDBStatement));
+    int rc = prepareStmt(mDBConnection->GetNativeConnection(), sql,
+                     &mAsyncStatement);
     if (rc != SQLITE_OK) {
       *_stmt = nsnull;
       return rc;
     }
 
 #ifdef PR_LOGGING
     PR_LOG(gStorageLog, PR_LOG_NOTICE,
            ("Cloned statement 0x%p to 0x%p", mDBStatement, mAsyncStatement));
@@ -610,17 +609,17 @@ Statement::ExecuteStep(PRBool *_moreResu
       PRInt32 srv;
       (void)error->GetResult(&srv);
       return convertResultCode(srv);
     }
 
     // We have bound, so now we can clear our array.
     mParamsArray = nsnull;
   }
-  int srv = ::sqlite3_step(mDBStatement);
+  int srv = stepStmt(mDBStatement);
 
 #ifdef PR_LOGGING
   if (srv != SQLITE_ROW && srv != SQLITE_DONE) {
       nsCAutoString errStr;
       (void)mDBConnection->GetLastErrorString(errStr);
       PR_LOG(gStorageLog, PR_LOG_DEBUG,
              ("Statement::ExecuteStep error: %s", errStr.get()));
   }
--- a/storage/test/Makefile.in
+++ b/storage/test/Makefile.in
@@ -49,16 +49,17 @@ MODULE = test_storage
 XPCSHELL_TESTS = unit
 
 CPP_UNIT_TESTS = \
   test_transaction_helper.cpp \
   test_statement_scoper.cpp \
   test_mutex.cpp \
   test_binding_params.cpp \
   test_true_async.cpp \
+  test_unlock_notify.cpp \
   $(NULL)
 
 ifdef MOZ_DEBUG
 # FIXME bug 523392: test_deadlock_detector doesn't like Windows
 # FIXME bug 523378: also fails on OS X
 ifneq (,$(filter-out WINNT WINCE Darwin,$(OS_ARCH)))
 CPP_UNIT_TESTS += \
   test_deadlock_detector.cpp \
--- a/storage/test/storage_test_harness.h
+++ b/storage/test/storage_test_harness.h
@@ -11,17 +11,17 @@
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is storage test code.
  *
  * The Initial Developer of the Original Code is
- * Mozilla Corporation.
+ * The Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -34,41 +34,70 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "TestHarness.h"
 #include "nsMemory.h"
+#include "nsDirectoryServiceDefs.h"
 #include "mozIStorageService.h"
 #include "mozIStorageConnection.h"
 
 static int gTotalTests = 0;
 static int gPassedTests = 0;
 
 #define do_check_true(aCondition) \
   PR_BEGIN_MACRO \
     gTotalTests++; \
-    if (aCondition) \
+    if (aCondition) { \
       gPassedTests++; \
-    else \
-      fail("Expected true, got false on line %d!", __LINE__); \
+    } else { \
+      fail("Expected true, got false at %s:%d!", __FILE__, __LINE__); \
+    } \
   PR_END_MACRO
 
 #define do_check_false(aCondition) \
   PR_BEGIN_MACRO \
     gTotalTests++; \
-    if (!aCondition) \
+    if (!aCondition) { \
       gPassedTests++; \
-    else \
-      fail("Expected false, got true on line %d!", __LINE__); \
+    } else { \
+      fail("Expected false, got true at %s:%d!", __FILE__, __LINE__); \
+    } \
   PR_END_MACRO
 
+#define do_check_success(aResult) \
+  do_check_true(NS_SUCCEEDED(aResult))
+
+#define do_check_eq(aFirst, aSecond) \
+  do_check_true(aFirst == aSecond)
+
 already_AddRefed<mozIStorageConnection>
 getMemoryDatabase()
 {
   nsCOMPtr<mozIStorageService> ss =
     do_GetService("@mozilla.org/storage/service;1");
   nsCOMPtr<mozIStorageConnection> conn;
-  (void)ss->OpenSpecialDatabase("memory", getter_AddRefs(conn));
+  nsresult rv = ss->OpenSpecialDatabase("memory", getter_AddRefs(conn));
+  do_check_success(rv);
   return conn.forget();
 }
+
+already_AddRefed<mozIStorageConnection>
+getDatabase()
+{
+  nsCOMPtr<nsIFile> dbFile;
+  (void)NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                               getter_AddRefs(dbFile));
+  NS_ASSERTION(dbFile, "The directory doesn't exists?!");
+
+  nsresult rv = dbFile->Append(NS_LITERAL_STRING("storage_test_db.sqlite"));
+  do_check_success(rv);
+
+  nsCOMPtr<mozIStorageService> ss =
+    do_GetService("@mozilla.org/storage/service;1");
+  nsCOMPtr<mozIStorageConnection> conn;
+  rv = ss->OpenDatabase(dbFile, getter_AddRefs(conn));
+  do_check_success(rv);
+  return conn.forget();
+}
new file mode 100644
--- /dev/null
+++ b/storage/test/test_unlock_notify.cpp
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim set:sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is storage test code.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "storage_test_harness.h"
+
+#include "mozilla/Monitor.h"
+#include "nsThreadUtils.h"
+#include "mozIStorageStatement.h"
+
+/**
+ * This file tests that our implementation around sqlite3_unlock_notify works
+ * as expected.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers
+
+enum State {
+  STARTING,
+  WRITE_LOCK,
+  READ_LOCK,
+  TEST_DONE
+};
+
+class DatabaseLocker : public nsRunnable
+{
+public:
+  DatabaseLocker(const char* aSQL)
+  : monitor("DatabaseLocker::monitor")
+  , mSQL(aSQL)
+  , mState(STARTING)
+  {
+  }
+
+  void RunInBackground()
+  {
+    (void)NS_NewThread(getter_AddRefs(mThread));
+    do_check_true(mThread);
+
+    do_check_success(mThread->Dispatch(this, NS_DISPATCH_NORMAL));
+  }
+
+  NS_IMETHOD Run()
+  {
+    mozilla::MonitorAutoEnter lock(monitor);
+
+    nsCOMPtr<mozIStorageConnection> db(getDatabase());
+
+    nsCString sql(mSQL);
+    nsCOMPtr<mozIStorageStatement> stmt;
+    do_check_success(db->CreateStatement(sql, getter_AddRefs(stmt)));
+
+    PRBool hasResult;
+    do_check_success(stmt->ExecuteStep(&hasResult));
+
+    Notify(WRITE_LOCK);
+    WaitFor(TEST_DONE);
+
+    return NS_OK;
+  }
+
+  void WaitFor(State aState)
+  {
+    monitor.AssertCurrentThreadIn();
+    while (mState != aState) {
+      do_check_success(monitor.Wait());
+    }
+  }
+
+  void Notify(State aState)
+  {
+    monitor.AssertCurrentThreadIn();
+    mState = aState;
+    do_check_success(monitor.Notify());
+  }
+
+  mozilla::Monitor monitor;
+
+protected:
+  nsCOMPtr<nsIThread> mThread;
+  const char *const mSQL;
+  State mState;
+};
+
+class DatabaseTester : public DatabaseLocker
+{
+public:
+  DatabaseTester(mozIStorageConnection *aConnection,
+                 const char* aSQL)
+  : DatabaseLocker(aSQL)
+  , mConnection(aConnection)
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    mozilla::MonitorAutoEnter lock(monitor);
+    WaitFor(READ_LOCK);
+
+    nsCString sql(mSQL);
+    nsCOMPtr<mozIStorageStatement> stmt;
+    do_check_success(mConnection->CreateStatement(sql, getter_AddRefs(stmt)));
+
+    PRBool hasResult;
+    nsresult rv = stmt->ExecuteStep(&hasResult);
+    do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
+
+    // Finalize our statement and null out our connection before notifying to
+    // ensure that we close on the proper thread.
+    rv = stmt->Finalize();
+    do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
+    mConnection = nsnull;
+
+    Notify(TEST_DONE);
+
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<mozIStorageConnection> mConnection;
+  State mState;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Test Functions
+
+void
+setup()
+{
+  nsCOMPtr<mozIStorageConnection> db(getDatabase());
+
+  // Create and populate a dummy table.
+  nsresult rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE test (id INTEGER PRIMARY KEY, data STRING)"
+  ));
+  do_check_success(rv);
+  rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO test (data) VALUES ('foo')"
+  ));
+  do_check_success(rv);
+  rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO test (data) VALUES ('bar')"
+  ));
+  do_check_success(rv);
+  rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE UNIQUE INDEX unique_data ON test (data)"
+  ));
+  do_check_success(rv);
+}
+
+void
+test_step_locked_does_not_block_main_thread()
+{
+  nsCOMPtr<mozIStorageConnection> db(getDatabase());
+
+  // Need to prepare our statement ahead of time so we make sure to only test
+  // step and not prepare.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
+    "INSERT INTO test (data) VALUES ('test1')"
+  ), getter_AddRefs(stmt));
+  do_check_success(rv);
+
+  nsRefPtr<DatabaseLocker> locker(new DatabaseLocker("SELECT * FROM test"));
+  do_check_true(locker);
+  mozilla::MonitorAutoEnter lock(locker->monitor);
+  locker->RunInBackground();
+
+  // Wait for the locker to notify us that it has locked the database properly.
+  locker->WaitFor(WRITE_LOCK);
+
+  PRBool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
+
+  locker->Notify(TEST_DONE);
+}
+
+void
+test_drop_index_does_not_loop()
+{
+  nsCOMPtr<mozIStorageConnection> db(getDatabase());
+
+  // Need to prepare our statement ahead of time so we make sure to only test
+  // step and not prepare.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM test"
+  ), getter_AddRefs(stmt));
+  do_check_success(rv);
+
+  nsRefPtr<DatabaseTester> tester =
+    new DatabaseTester(db, "DROP INDEX unique_data");
+  do_check_true(tester);
+  mozilla::MonitorAutoEnter lock(tester->monitor);
+  tester->RunInBackground();
+
+  // Hold a read lock on the database, and then let the tester try to execute.
+  PRBool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  do_check_success(rv);
+  do_check_true(hasResult);
+  tester->Notify(READ_LOCK);
+
+  // Make sure the tester finishes its test before we move on.
+  tester->WaitFor(TEST_DONE);
+}
+
+void
+test_drop_table_does_not_loop()
+{
+  nsCOMPtr<mozIStorageConnection> db(getDatabase());
+
+  // Need to prepare our statement ahead of time so we make sure to only test
+  // step and not prepare.
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT * FROM test"
+  ), getter_AddRefs(stmt));
+  do_check_success(rv);
+
+  nsRefPtr<DatabaseTester> tester(new DatabaseTester(db, "DROP TABLE test"));
+  do_check_true(tester);
+  mozilla::MonitorAutoEnter lock(tester->monitor);
+  tester->RunInBackground();
+
+  // Hold a read lock on the database, and then let the tester try to execute.
+  PRBool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  do_check_success(rv);
+  do_check_true(hasResult);
+  tester->Notify(READ_LOCK);
+
+  // Make sure the tester finishes its test before we move on.
+  tester->WaitFor(TEST_DONE);
+}
+
+void (*gTests[])(void) = {
+  setup,
+  test_step_locked_does_not_block_main_thread,
+  test_drop_index_does_not_loop,
+  test_drop_table_does_not_loop,
+};
+
+const char *file = __FILE__;
+#define TEST_NAME "sqlite3_unlock_notify"
+#define TEST_FILE file
+#include "storage_test_harness_tail.h"