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
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"