Bug 536978 - Cookies should write to the database asynchronously.
authorShawn Wilsher <sdwilsh@shawnwilsher.com>
Fri, 12 Mar 2010 07:18:38 -0800
changeset 39462 3c689de8ee14bb296b52018cedad6efabc932350
parent 39461 cbf4b59a607dd0236a5c2aafa76a3d42efe549cf
child 39463 55b6bc4c0b92941f8be631c20555f9a0d0f5f9fa
push idunknown
push userunknown
push dateunknown
bugs536978
milestone1.9.3a4pre
Bug 536978 - Cookies should write to the database asynchronously. This makes cookie insertion, updates, and deletions happen asynchronously off of the main thread. The only I/O done on the main thread is the initial loading of the database at each startup. r=dwitte
netwerk/cookie/src/nsCookieService.cpp
netwerk/cookie/src/nsCookieService.h
storage/public/storage.h
--- a/netwerk/cookie/src/nsCookieService.cpp
+++ b/netwerk/cookie/src/nsCookieService.cpp
@@ -64,20 +64,19 @@
 #include "nsAutoPtr.h"
 #include "nsReadableUtils.h"
 #include "nsCRT.h"
 #include "prtime.h"
 #include "prprf.h"
 #include "nsNetUtil.h"
 #include "nsNetCID.h"
 #include "nsAppDirectoryServiceDefs.h"
-#include "mozIStorageService.h"
-#include "mozStorageHelper.h"
 #include "nsIPrivateBrowsingService.h"
 #include "nsNetCID.h"
+#include "mozilla/storage.h"
 
 /******************************************************************************
  * nsCookieService impl:
  * useful types & constants
  ******************************************************************************/
 
 // XXX_hack. See bug 178993.
 // This is a hack to hide HttpOnly cookies from older browsers
@@ -337,16 +336,137 @@ LogSuccess(PRBool aSetCookie, nsIURI *aH
 
 #else
 #define COOKIE_LOGFAILURE(a, b, c, d)    PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
 #define COOKIE_LOGSUCCESS(a, b, c, d, e) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
 #define COOKIE_LOGEVICTED(a)             PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
 #define COOKIE_LOGSTRING(a, b)           PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
 #endif
 
+#ifdef DEBUG
+#define NS_ASSERT_SUCCESS(res)                                               \
+  PR_BEGIN_MACRO                                                             \
+  nsresult __rv = res; /* Do not evaluate |res| more than once! */           \
+  if (NS_FAILED(__rv)) {                                                     \
+    char *msg = PR_smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%X", \
+                            #res, __rv);                                     \
+    NS_ASSERTION(NS_SUCCEEDED(__rv), msg);                                   \
+    PR_smprintf_free(msg);                                                   \
+  }                                                                          \
+  PR_END_MACRO
+#else
+#define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO
+#endif
+
+namespace {
+/******************************************************************************
+ * DBListenerErrorHandler imp:
+ * Parent class for our async storage listeners that handles the logging of
+ * errors.
+ ******************************************************************************/
+class DBListenerErrorHandler : public mozIStorageStatementCallback
+{
+protected:
+  virtual const char *GetOpType() = 0;
+
+public:
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD HandleError(mozIStorageError* aError)
+  {
+#ifdef PR_LOGGING
+    PRInt32 result = -1;
+    aError->GetResult(&result);
+    nsCAutoString message;
+    aError->GetMessage(message);
+    COOKIE_LOGSTRING(PR_LOG_WARNING,
+                     ("Error %d occurred while performing a %s operation.  Message: `%s`\n",
+                      result, GetOpType(), message.get()));
+#endif
+    return NS_OK;
+  }
+};
+NS_IMETHODIMP_(nsrefcnt) DBListenerErrorHandler::AddRef() { return 2; }
+NS_IMETHODIMP_(nsrefcnt) DBListenerErrorHandler::Release() { return 1; }
+NS_IMPL_QUERY_INTERFACE1(DBListenerErrorHandler, mozIStorageStatementCallback)
+
+/******************************************************************************
+ * InsertCookieDBListener imp:
+ * Static mozIStorageStatementCallback used to track asynchronous insertion
+ * operations.
+ ******************************************************************************/
+class InsertCookieDBListener : public DBListenerErrorHandler
+{
+protected:
+  virtual const char *GetOpType() { return "INSERT"; }
+
+public:
+  NS_IMETHOD HandleResult(mozIStorageResultSet*)
+  {
+    NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult");
+    return NS_OK;
+  }
+  NS_IMETHOD HandleCompletion(PRUint16 aReason)
+  {
+    return NS_OK;
+  }
+};
+
+static InsertCookieDBListener sInsertCookieDBListener;
+
+/******************************************************************************
+ * UpdateCookieDBListener imp:
+ * Static mozIStorageStatementCallback used to track asynchronous update
+ * operations.
+ ******************************************************************************/
+class UpdateCookieDBListener : public DBListenerErrorHandler
+{
+protected:
+  virtual const char *GetOpType() { return "UPDATE"; }
+
+public:
+  NS_IMETHOD HandleResult(mozIStorageResultSet*)
+  {
+    NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult");
+    return NS_OK;
+  }
+  NS_IMETHOD HandleCompletion(PRUint16 aReason)
+  {
+    return NS_OK;
+  }
+};
+
+static UpdateCookieDBListener sUpdateCookieDBListener;
+
+/******************************************************************************
+ * RemoveCookieDBListener imp:
+ * Static mozIStorageStatementCallback used to track asynchronous removal
+ * operations.
+ ******************************************************************************/
+class RemoveCookieDBListener :  public DBListenerErrorHandler
+{
+protected:
+  virtual const char *GetOpType() { return "REMOVE"; }
+
+public:
+  NS_IMETHOD HandleResult(mozIStorageResultSet*)
+  {
+    NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult");
+    return NS_OK;
+  }
+  NS_IMETHOD HandleCompletion(PRUint16 aReason)
+  {
+    return NS_OK;
+  }
+};
+
+static RemoveCookieDBListener sRemoveCookieDBListener;
+
+} // anonymous namespace
+
 /******************************************************************************
  * nsCookieService impl:
  * singleton instance ctor/dtor methods
  ******************************************************************************/
 
 nsCookieService *nsCookieService::gCookieService = nsnull;
 
 nsCookieService*
@@ -668,17 +788,20 @@ nsCookieService::CloseDB()
 {
   NS_ASSERTION(!mPrivateDBState.dbConn, "private DB connection should always be null");
 
   // finalize our statements and close the db connection for the default state.
   // since we own these objects, nulling the pointers is sufficient here.
   mDefaultDBState.stmtInsert = nsnull;
   mDefaultDBState.stmtDelete = nsnull;
   mDefaultDBState.stmtUpdate = nsnull;
-  mDefaultDBState.dbConn = nsnull;
+  if (mDefaultDBState.dbConn) {
+    mDefaultDBState.dbConn->AsyncClose(NULL);
+    mDefaultDBState.dbConn = nsnull;
+  }
 }
 
 nsCookieService::~nsCookieService()
 {
   CloseDB();
 
   gCookieService = nsnull;
 }
@@ -846,24 +969,43 @@ nsCookieService::SetCookieStringInternal
   PRInt64 serverTime;
   if (aServerTime &&
       PR_ParseTimeString(aServerTime, PR_TRUE, &tempServerTime) == PR_SUCCESS) {
     serverTime = tempServerTime / PR_USEC_PER_SEC;
   } else {
     serverTime = PR_Now() / PR_USEC_PER_SEC;
   }
 
-  // start a transaction on the storage db, to optimize insertions.
-  // transaction will automically commit on completion
-  mozStorageTransaction transaction(mDBState->dbConn, PR_TRUE);
- 
+  // We may be adding a bunch of cookies to the DB, so we use async batching
+  // with storage to make this super fast.
+  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+  if (mDBState->dbConn) {
+    mDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
+  }
+
   // switch to a nice string type now, and process each cookie in the header
   nsDependentCString cookieHeader(aCookieHeader);
   while (SetCookieInternal(aHostURI, aChannel, baseDomain, requireHostMatch,
-                           cookieHeader, serverTime, aFromHttp));
+                           cookieHeader, serverTime, aFromHttp, paramsArray));
+
+  // If we had a params array, go ahead and write it out to disk now.
+  if (paramsArray) {
+    // ...but only if we have sufficient length!
+    PRUint32 length;
+    paramsArray->GetLength(&length);
+    if (length == 0)
+      return NS_OK;
+
+    rv = mDBState->stmtInsert->BindParameters(paramsArray);
+    NS_ASSERT_SUCCESS(rv);
+    nsCOMPtr<mozIStoragePendingStatement> handle;
+    rv = mDBState->stmtInsert->ExecuteAsync(&sInsertCookieDBListener,
+                                            getter_AddRefs(handle));
+    NS_ASSERT_SUCCESS(rv);
+  }
 
   return NS_OK;
 }
 
 // notify observers that a cookie was rejected due to the users' prefs.
 void
 nsCookieService::NotifyRejected(nsIURI *aHostURI)
 {
@@ -1132,17 +1274,17 @@ nsCookieService::Read()
                        lastAccessed,
                        creationID,
                        PR_FALSE,
                        isSecure,
                        isHttpOnly);
     if (!newCookie)
       return NS_ERROR_OUT_OF_MEMORY;
 
-    if (!AddCookieToList(baseDomain, newCookie, PR_FALSE))
+    if (!AddCookieToList(baseDomain, newCookie, NULL, PR_FALSE))
       // It is purpose that created us; purpose that connects us;
       // purpose that pulls us; that guides us; that drives us.
       // It is purpose that defines us; purpose that binds us.
       // When a cookie no longer has purpose, it has a choice:
       // it can return to the source to be deleted, or it can go
       // into exile, and stay hidden inside the Matrix.
       // Let's choose deletion.
       delete newCookie;
@@ -1159,20 +1301,16 @@ nsCookieService::ImportCookies(nsIFile *
   nsresult rv;
   nsCOMPtr<nsIInputStream> fileInputStream;
   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile);
   if (NS_FAILED(rv)) return rv;
 
   nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
   if (NS_FAILED(rv)) return rv;
 
-  // start a transaction on the storage db, to optimize insertions.
-  // transaction will automically commit on completion
-  mozStorageTransaction transaction(mDBState->dbConn, PR_TRUE);
-
   static const char kTrue[] = "TRUE";
 
   nsCAutoString buffer, baseDomain;
   PRBool isMore = PR_TRUE;
   PRInt32 hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex;
   nsASingleFragmentCString::char_iterator iter;
   PRInt32 numInts;
   PRInt64 expires;
@@ -1203,16 +1341,23 @@ nsCookieService::ImportCookies(nsIFile *
    * in a comment, so they don't expose HttpOnly cookies to JS.
    *
    * The format for HttpOnly cookies is
    *
    * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie
    *
    */
 
+  // We will likely be adding a bunch of cookies to the DB, so we use async
+  // batching with storage to make this super fast.
+  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+  if (mDBState->dbConn) {
+    mDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray));
+  }
+
   while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
     if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) {
       isHttpOnly = PR_TRUE;
       hostIndex = sizeof(kHttpOnlyPrefix) - 1;
     } else if (buffer.IsEmpty() || buffer.First() == '#') {
       continue;
     } else {
       isHttpOnly = PR_FALSE;
@@ -1273,22 +1418,40 @@ nsCookieService::ImportCookies(nsIFile *
     if (!newCookie) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
     
     // trick: preserve the most-recently-used cookie ordering,
     // by successively decrementing the lastAccessed time
     lastAccessedCounter--;
 
-    if (originalCookieCount == 0)
-      AddCookieToList(baseDomain, newCookie);
-    else
-      AddInternal(baseDomain, newCookie, currentTimeInUsec, nsnull, nsnull, PR_TRUE);
+    if (originalCookieCount == 0) {
+      AddCookieToList(baseDomain, newCookie, paramsArray);
+    }
+    else {
+      AddInternal(baseDomain, newCookie, currentTimeInUsec, NULL, NULL, PR_TRUE,
+                  paramsArray);
+    }
   }
 
+  // If we need to write to disk, do so now.
+  if (paramsArray) {
+    PRUint32 length;
+    paramsArray->GetLength(&length);
+    if (length) {
+      rv = mDBState->stmtInsert->BindParameters(paramsArray);
+      NS_ASSERT_SUCCESS(rv);
+      nsCOMPtr<mozIStoragePendingStatement> handle;
+      rv = mDBState->stmtInsert->ExecuteAsync(&sInsertCookieDBListener,
+                                              getter_AddRefs(handle));
+      NS_ASSERT_SUCCESS(rv);
+    }
+  }
+
+
   COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported", mDBState->cookieCount));
 
   return NS_OK;
 }
 
 /******************************************************************************
  * nsCookieService impl:
  * private GetCookie/SetCookie helpers
@@ -1442,25 +1605,41 @@ nsCookieService::GetCookieInternal(nsIUR
 
   PRInt32 count = foundCookieList.Length();
   if (count == 0)
     return;
 
   // update lastAccessed timestamps. we only do this if the timestamp is stale
   // by a certain amount, to avoid thrashing the db during pageload.
   if (stale) {
-    // start a transaction on the storage db, to optimize updates.
-    // transaction will automically commit on completion.
-    mozStorageTransaction transaction(mDBState->dbConn, PR_TRUE);
+    // Create an array of parameters to bind to our update statement.
+    nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+    mozIStorageStatement* stmt = mDBState->stmtUpdate;
+    if (mDBState->dbConn) {
+      stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+    }
 
     for (PRInt32 i = 0; i < count; ++i) {
       cookie = foundCookieList.ElementAt(i);
 
       if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold)
-        UpdateCookieInList(cookie, currentTimeInUsec);
+        UpdateCookieInList(cookie, currentTimeInUsec, paramsArray);
+    }
+    // Update the database now if necessary.
+    if (paramsArray) {
+      PRUint32 length;
+      paramsArray->GetLength(&length);
+      if (length) {
+        nsresult rv = stmt->BindParameters(paramsArray);
+        NS_ASSERT_SUCCESS(rv);
+        nsCOMPtr<mozIStoragePendingStatement> handle;
+        rv = stmt->ExecuteAsync(&sUpdateCookieDBListener,
+                                getter_AddRefs(handle));
+        NS_ASSERT_SUCCESS(rv);
+      }
     }
   }
 
   // return cookies in order of path length; longest to shortest.
   // this is required per RFC2109.  if cookies match in length,
   // then sort by creation time (see bug 236772).
   foundCookieList.Sort(CompareCookiesForSending());
 
@@ -1492,23 +1671,24 @@ nsCookieService::GetCookieInternal(nsIUR
     COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, cookieData, nsnull, nsnull);
     *aCookie = ToNewCString(cookieData);
   }
 }
 
 // processes a single cookie, and returns PR_TRUE if there are more cookies
 // to be processed
 PRBool
-nsCookieService::SetCookieInternal(nsIURI             *aHostURI,
-                                   nsIChannel         *aChannel,
-                                   const nsCString    &aBaseDomain,
-                                   PRBool              aRequireHostMatch,
-                                   nsDependentCString &aCookieHeader,
-                                   PRInt64             aServerTime,
-                                   PRBool              aFromHttp)
+nsCookieService::SetCookieInternal(nsIURI                        *aHostURI,
+                                   nsIChannel                    *aChannel,
+                                   const nsCString               &aBaseDomain,
+                                   PRBool                         aRequireHostMatch,
+                                   nsDependentCString            &aCookieHeader,
+                                   PRInt64                        aServerTime,
+                                   PRBool                         aFromHttp,
+                                   mozIStorageBindingParamsArray *aParamsArray)
 {
   // create a stack-based nsCookieAttributes, to store all the
   // attributes parsed from the cookie
   nsCookieAttributes cookieAttributes;
 
   // init expiryTime such that session cookies won't prematurely expire
   cookieAttributes.expiryTime = LL_MAXINT;
 
@@ -1583,46 +1763,42 @@ nsCookieService::SetCookieInternal(nsIUR
     // update isSession and expiry attributes, in case they changed
     cookie->SetIsSession(cookieAttributes.isSession);
     cookie->SetExpiry(cookieAttributes.expiryTime);
   }
 
   // add the cookie to the list. AddInternal() takes care of logging.
   // we get the current time again here, since it may have changed during prompting
   AddInternal(aBaseDomain, cookie, PR_Now(), aHostURI, savedCookieHeader.get(),
-              aFromHttp);
+              aFromHttp, aParamsArray);
   return newCookie;
 }
 
 // this is a backend function for adding a cookie to the list, via SetCookie.
 // also used in the cookie manager, for profile migration from IE.
 // it either replaces an existing cookie; or adds the cookie to the hashtable,
 // and deletes a cookie (if maximum number of cookies has been
 // reached). also performs list maintenance by removing expired cookies.
 void
-nsCookieService::AddInternal(const nsCString &aBaseDomain,
-                             nsCookie        *aCookie,
-                             PRInt64          aCurrentTimeInUsec,
-                             nsIURI          *aHostURI,
-                             const char      *aCookieHeader,
-                             PRBool           aFromHttp)
+nsCookieService::AddInternal(const nsCString               &aBaseDomain,
+                             nsCookie                      *aCookie,
+                             PRInt64                        aCurrentTimeInUsec,
+                             nsIURI                        *aHostURI,
+                             const char                    *aCookieHeader,
+                             PRBool                         aFromHttp,
+                             mozIStorageBindingParamsArray *aParamsArray)
 {
   PRInt64 currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC;
 
   // if the new cookie is httponly, make sure we're not coming from script
   if (!aFromHttp && aCookie->IsHttpOnly()) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, "cookie is httponly; coming from script");
     return;
   }
 
-  // start a transaction on the storage db, to optimize deletions/insertions.
-  // transaction will automically commit on completion. if we already have a
-  // transaction (e.g. from SetCookie*()), this will have no effect. 
-  mozStorageTransaction transaction(mDBState->dbConn, PR_TRUE);
-
   nsListIter matchIter;
   PRBool foundCookie = FindCookie(aBaseDomain, aCookie->Host(),
     aCookie->Name(), aCookie->Path(), matchIter, currentTime);
 
   nsRefPtr<nsCookie> oldCookie;
   if (foundCookie) {
     oldCookie = matchIter.Cookie();
 
@@ -1673,17 +1849,17 @@ nsCookieService::AddInternal(const nsCSt
         // note that the cookieOldestTime indicator can be pessimistic - if it's
         // older than the actual oldest cookie, we'll just purge more eagerly.
         PurgeCookies(aCurrentTimeInUsec);
       }
     }
   }
 
   // add the cookie to head of list
-  AddCookieToList(aBaseDomain, aCookie);
+  AddCookieToList(aBaseDomain, aCookie, aParamsArray);
   NotifyChanged(aCookie, foundCookie ? NS_LITERAL_STRING("changed").get()
                                      : NS_LITERAL_STRING("added").get());
 
   COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie != nsnull);
 }
 
 /******************************************************************************
  * nsCookieService impl:
@@ -2277,37 +2453,44 @@ nsCookieService::RemoveAllFromMemory()
 // since enumeration is done using callback functions
 struct nsPurgeData
 {
   typedef nsTArray<nsListIter> ArrayType;
 
   nsPurgeData(PRInt64 aCurrentTime,
               PRInt64 aPurgeTime,
               ArrayType &aPurgeList,
-              nsIMutableArray *aRemovedList)
+              nsIMutableArray *aRemovedList,
+              mozIStorageBindingParamsArray *aParamsArray)
    : currentTime(aCurrentTime)
    , purgeTime(aPurgeTime)
    , oldestTime(LL_MAXINT)
    , purgeList(aPurgeList)
-   , removedList(aRemovedList) {}
+   , removedList(aRemovedList)
+   , paramsArray(aParamsArray)
+  {
+  }
 
   // the current time, in seconds
   PRInt64 currentTime;
 
   // lastAccessed time older than which cookies are eligible for purge
   PRInt64 purgeTime;
 
   // lastAccessed time of the oldest cookie found during purge, to update our indicator
   PRInt64 oldestTime;
 
   // list of cookies over the age limit, for purging
   ArrayType &purgeList;
 
   // list of all cookies we've removed, for notification
   nsIMutableArray *removedList;
+
+  // The array of parameters to be bound to the statement for deletion later.
+  mozIStorageBindingParamsArray *paramsArray;
 };
 
 // comparator class for lastaccessed times of cookies.
 class CompareCookiesByAge {
 public:
   PRBool Equals(const nsListIter &a, const nsListIter &b) const
   {
     // CreationID is unique, so two id's can never be equal.
@@ -2345,27 +2528,28 @@ public:
 
 PLDHashOperator
 purgeCookiesCallback(nsCookieEntry *aEntry,
                      void          *aArg)
 {
   nsPurgeData &data = *static_cast<nsPurgeData*>(aArg);
 
   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
+  mozIStorageBindingParamsArray *array = data.paramsArray;
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) {
     nsListIter iter(aEntry, i);
     nsCookie *cookie = cookies[i];
 
     // check if the cookie has expired
     if (cookie->Expiry() <= data.currentTime) {
       data.removedList->AppendElement(cookie, PR_FALSE);
       COOKIE_LOGEVICTED(cookie);
 
       // remove from list; do not increment our iterator
-      nsCookieService::gCookieService->RemoveCookieFromList(iter);
+      nsCookieService::gCookieService->RemoveCookieFromList(iter, array);
 
     } else {
       // check if the cookie is over the age limit
       if (cookie->LastAccessed() <= data.purgeTime) {
         data.purgeList.AppendElement(iter);
 
       } else if (cookie->LastAccessed() < data.oldestTime) {
         // reset our indicator
@@ -2391,18 +2575,24 @@ nsCookieService::PurgeCookies(PRInt64 aC
 #endif
 
   nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList;
 
   nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID);
   if (!removedList)
     return;
 
+  mozIStorageStatement *stmt = mDBState->stmtDelete;
+  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+  if (mDBState->dbConn) {
+    stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+  }
+
   nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC,
-    aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList);
+    aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray);
   mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data);
 
 #ifdef PR_LOGGING
   PRUint32 postExpiryCookieCount = mDBState->cookieCount;
 #endif
 
   // now we have a list of iterators for cookies over the age limit.
   // sort them by age, and then we'll see how many to remove...
@@ -2421,17 +2611,30 @@ nsCookieService::PurgeCookies(PRInt64 aC
   // together, and with ascending index. this allows us to iterate backwards
   // over the list removing cookies, without having to adjust indexes as we go.
   purgeList.Sort(CompareCookiesByIndex());
   for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) {
     nsCookie *cookie = purgeList[i].Cookie();
     removedList->AppendElement(cookie, PR_FALSE);
     COOKIE_LOGEVICTED(cookie);
 
-    RemoveCookieFromList(purgeList[i]);
+    RemoveCookieFromList(purgeList[i], paramsArray);
+  }
+
+  // Update the database if we have entries to purge.
+  if (paramsArray) {
+    PRUint32 length;
+    paramsArray->GetLength(&length);
+    if (length) {
+      nsresult rv = stmt->BindParameters(paramsArray);
+      NS_ASSERT_SUCCESS(rv);
+      nsCOMPtr<mozIStoragePendingStatement> handle;
+      rv = stmt->ExecuteAsync(&sRemoveCookieDBListener, getter_AddRefs(handle));
+      NS_ASSERT_SUCCESS(rv);
+    }
   }
 
   // take all the cookies in the removed list, and notify about them in one batch
   NotifyChanged(removedList, NS_LITERAL_STRING("batch-deleted").get());
 
   // reset the oldest time indicator
   mDBState->cookieOldestTime = data.oldestTime;
 
@@ -2580,33 +2783,46 @@ nsCookieService::FindCookie(const nsCStr
     }
   }
 
   return PR_FALSE;
 }
 
 // remove a cookie from the hashtable, and update the iterator state.
 void
-nsCookieService::RemoveCookieFromList(const nsListIter &aIter)
+nsCookieService::RemoveCookieFromList(const nsListIter              &aIter,
+                                      mozIStorageBindingParamsArray *aParamsArray)
 {
   // if it's a non-session cookie, remove it from the db
   if (!aIter.Cookie()->IsSession() && mDBState->dbConn) {
-    // use our cached sqlite "delete" statement
-    mozStorageStatementScoper scoper(mDBState->stmtDelete);
-
-    PRInt64 creationID = aIter.Cookie()->CreationID();
-    nsresult rv = mDBState->stmtDelete->BindInt64Parameter(0, creationID);
-    if (NS_SUCCEEDED(rv)) {
-      PRBool hasResult;
-      rv = mDBState->stmtDelete->ExecuteStep(&hasResult);
+    // Use the asynchronous binding methods to ensure that we do not acquire
+    // the database lock.
+    mozIStorageStatement *stmt = mDBState->stmtDelete;
+    nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray);
+    if (!paramsArray) {
+      stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
     }
 
-    if (NS_FAILED(rv)) {
-      NS_WARNING("db remove failed!");
-      COOKIE_LOGSTRING(PR_LOG_WARNING, ("RemoveCookieFromList(): removing from db gave error %x", rv));
+    nsCOMPtr<mozIStorageBindingParams> params;
+    paramsArray->NewBindingParams(getter_AddRefs(params));
+
+    nsresult rv = params->BindInt64ByIndex(0, aIter.Cookie()->CreationID());
+    NS_ASSERT_SUCCESS(rv);
+
+    rv = paramsArray->AddParams(params);
+    NS_ASSERT_SUCCESS(rv);
+
+    // If we weren't given a params array, we'll need to remove it ourselves.
+    if (!aParamsArray) {
+      rv = stmt->BindParameters(paramsArray);
+      NS_ASSERT_SUCCESS(rv);
+      nsCOMPtr<mozIStoragePendingStatement> handle;
+      rv = stmt->ExecuteAsync(&sRemoveCookieDBListener,
+                              getter_AddRefs(handle));
+      NS_ASSERT_SUCCESS(rv);
     }
   }
 
   if (aIter.entry->GetCookies().Length() == 1) {
     // we're removing the last element in the array - so just remove the entry
     // from the hash. note that the entryclass' dtor will take care of
     // releasing this last element for us!
     mDBState->hostTable.RawRemoveEntry(aIter.entry);
@@ -2614,106 +2830,127 @@ nsCookieService::RemoveCookieFromList(co
   } else {
     // just remove the element from the list
     aIter.entry->GetCookies().RemoveElementAt(aIter.index);
   }
 
   --mDBState->cookieCount;
 }
 
-static nsresult
-bindCookieParameters(mozIStorageStatement *aStmt, const nsCookie *aCookie)
+static void
+bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray,
+                     const nsCookie *aCookie)
 {
+  NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!");
+  NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!");
   nsresult rv;
 
-  rv = aStmt->BindInt64Parameter(0, aCookie->CreationID());
-  if (NS_FAILED(rv)) return rv;
-
-  rv = aStmt->BindUTF8StringParameter(1, aCookie->Name());
-  if (NS_FAILED(rv)) return rv;
-  
-  rv = aStmt->BindUTF8StringParameter(2, aCookie->Value());
-  if (NS_FAILED(rv)) return rv;
-  
-  rv = aStmt->BindUTF8StringParameter(3, aCookie->Host());
-  if (NS_FAILED(rv)) return rv;
-  
-  rv = aStmt->BindUTF8StringParameter(4, aCookie->Path());
-  if (NS_FAILED(rv)) return rv;
-  
-  rv = aStmt->BindInt64Parameter(5, aCookie->Expiry());
-  if (NS_FAILED(rv)) return rv;
-  
-  rv = aStmt->BindInt64Parameter(6, aCookie->LastAccessed());
-  if (NS_FAILED(rv)) return rv;
-  
-  rv = aStmt->BindInt32Parameter(7, aCookie->IsSecure());
-  if (NS_FAILED(rv)) return rv;
-  
-  rv = aStmt->BindInt32Parameter(8, aCookie->IsHttpOnly());
-  return rv;
+  // Use the asynchronous binding methods to ensure that we do not acquire the
+  // database lock.
+  nsCOMPtr<mozIStorageBindingParams> params;
+  rv = aParamsArray->NewBindingParams(getter_AddRefs(params));
+  NS_ASSERT_SUCCESS(rv);
+
+  // Bind our values to params
+  rv = params->BindInt64ByIndex(0, aCookie->CreationID());
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindUTF8StringByIndex(1, aCookie->Name());
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindUTF8StringByIndex(2, aCookie->Value());
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindUTF8StringByIndex(3, aCookie->Host());
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindUTF8StringByIndex(4, aCookie->Path());
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindInt64ByIndex(5, aCookie->Expiry());
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindInt64ByIndex(6, aCookie->LastAccessed());
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindInt32ByIndex(7, aCookie->IsSecure());
+  NS_ASSERT_SUCCESS(rv);
+
+  rv = params->BindInt32ByIndex(8, aCookie->IsHttpOnly());
+  NS_ASSERT_SUCCESS(rv);
+
+  // Bind the params to the array.
+  rv = aParamsArray->AddParams(params);
+  NS_ASSERT_SUCCESS(rv);
 }
 
 PRBool
-nsCookieService::AddCookieToList(const nsCString &aBaseDomain,
-                                 nsCookie        *aCookie,
-                                 PRBool           aWriteToDB)
+nsCookieService::AddCookieToList(const nsCString               &aBaseDomain,
+                                 nsCookie                      *aCookie,
+                                 mozIStorageBindingParamsArray *aParamsArray,
+                                 PRBool                         aWriteToDB)
 {
+  NS_ASSERTION(!(mDBState->dbConn && !aWriteToDB && aParamsArray),
+               "Not writing to the DB but have a params array?");
+  NS_ASSERTION(!(!mDBState->dbConn && aParamsArray),
+               "Do not have a DB connection but have a params array?");
+
   nsCookieEntry *entry = mDBState->hostTable.PutEntry(aBaseDomain);
   if (!entry) {
     NS_ERROR("can't insert element into a null entry!");
     return PR_FALSE;
   }
 
   entry->GetCookies().AppendElement(aCookie);
   ++mDBState->cookieCount;
 
   // keep track of the oldest cookie, for when it comes time to purge
   if (aCookie->LastAccessed() < mDBState->cookieOldestTime)
     mDBState->cookieOldestTime = aCookie->LastAccessed();
 
   // if it's a non-session cookie and hasn't just been read from the db, write it out.
   if (aWriteToDB && !aCookie->IsSession() && mDBState->dbConn) {
-    // use our cached sqlite "insert" statement
-    mozStorageStatementScoper scoper(mDBState->stmtInsert);
-
-    nsresult rv = bindCookieParameters(mDBState->stmtInsert, aCookie);
-    if (NS_SUCCEEDED(rv)) {
-      PRBool hasResult;
-      rv = mDBState->stmtInsert->ExecuteStep(&hasResult);
+    nsCOMPtr<mozIStorageBindingParamsArray> array(aParamsArray);
+    if (!array) {
+      mDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(array));
     }
-
-    if (NS_FAILED(rv)) {
-      NS_WARNING("db insert failed!");
-      COOKIE_LOGSTRING(PR_LOG_WARNING, ("AddCookieToList(): adding to db gave error %x", rv));
+    bindCookieParameters(array, aCookie);
+
+    // If we were supplied an array to store parameters, we shouldn't call
+    // executeAsync - someone up the stack will do this for us.
+    if (!aParamsArray) {
+      nsCOMPtr<mozIStoragePendingStatement> handle;
+      nsresult rv = mDBState->stmtInsert->ExecuteAsync(&sInsertCookieDBListener,
+                                                       getter_AddRefs(handle));
+      NS_ASSERT_SUCCESS(rv);
     }
   }
 
   return PR_TRUE;
 }
 
 void
-nsCookieService::UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed)
+nsCookieService::UpdateCookieInList(nsCookie                      *aCookie,
+                                    PRInt64                        aLastAccessed,
+                                    mozIStorageBindingParamsArray *aParamsArray)
 {
-  // update the lastAccessed timestamp
+  NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!");
+
+  // udpate the lastAccessed timestamp
   aCookie->SetLastAccessed(aLastAccessed);
 
   // if it's a non-session cookie, update it in the db too
-  if (!aCookie->IsSession() && mDBState->dbConn) {
-    // use our cached sqlite "update" statement
-    mozStorageStatementScoper scoper(mDBState->stmtUpdate);
-
-    nsresult rv = mDBState->stmtUpdate->BindInt64Parameter(0, aLastAccessed);
-    if (NS_SUCCEEDED(rv)) {
-      rv = mDBState->stmtUpdate->BindInt64Parameter(1, aCookie->CreationID());
-      if (NS_SUCCEEDED(rv)) {
-        PRBool hasResult;
-        rv = mDBState->stmtUpdate->ExecuteStep(&hasResult);
-      }
-    }
-
-    if (NS_FAILED(rv)) {
-      NS_WARNING("db update failed!");
-      COOKIE_LOGSTRING(PR_LOG_WARNING, ("UpdateCookieInList(): updating db gave error %x", rv));
-    }
+  if (!aCookie->IsSession() && aParamsArray) {
+    // Create our params holder.
+    nsCOMPtr<mozIStorageBindingParams> params;
+    aParamsArray->NewBindingParams(getter_AddRefs(params));
+
+    // Bind our parameters.
+    nsresult rv = params->BindInt64ByIndex(0, aLastAccessed);
+    NS_ASSERT_SUCCESS(rv);
+    rv = params->BindInt64ByIndex(1, aCookie->CreationID());
+    NS_ASSERT_SUCCESS(rv);
+
+    // Add our bound parameters to the array.
+    rv = aParamsArray->AddParams(params);
+    NS_ASSERT_SUCCESS(rv);
   }
 }
-
--- a/netwerk/cookie/src/nsCookieService.h
+++ b/netwerk/cookie/src/nsCookieService.h
@@ -169,21 +169,21 @@ class nsCookieService : public nsICookie
     nsresult                      CreateTable();
     void                          CloseDB();
     nsresult                      Read();
     nsresult                      NormalizeHost(nsCString &aHost);
     nsresult                      GetBaseDomain(nsIURI *aHostURI, nsCString &aBaseDomain, PRBool &aRequireHostMatch);
     nsresult                      GetBaseDomainFromHost(const nsACString &aHost, nsCString &aBaseDomain);
     void                          GetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, PRBool aHttpBound, char **aCookie);
     nsresult                      SetCookieStringInternal(nsIURI *aHostURI, nsIPrompt *aPrompt, const char *aCookieHeader, const char *aServerTime, nsIChannel *aChannel, PRBool aFromHttp);
-    PRBool                        SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, const nsCString& aBaseDomain, PRBool aRequireHostMatch, nsDependentCString &aCookieHeader, PRInt64 aServerTime, PRBool aFromHttp);
-    void                          AddInternal(const nsCString& aBaseDomain, nsCookie *aCookie, PRInt64 aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp);
-    void                          RemoveCookieFromList(const nsListIter &aIter);
-    PRBool                        AddCookieToList(const nsCString& aBaseDomain, nsCookie *aCookie, PRBool aWriteToDB = PR_TRUE);
-    void                          UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed);
+    PRBool                        SetCookieInternal(nsIURI *aHostURI, nsIChannel *aChannel, const nsCString& aBaseDomain, PRBool aRequireHostMatch, nsDependentCString &aCookieHeader, PRInt64 aServerTime, PRBool aFromHttp, mozIStorageBindingParamsArray *aParamsArray = NULL);
+    void                          AddInternal(const nsCString& aBaseDomain, nsCookie *aCookie, PRInt64 aCurrentTimeInUsec, nsIURI *aHostURI, const char *aCookieHeader, PRBool aFromHttp, mozIStorageBindingParamsArray *aParamsArray = NULL);
+    void                          RemoveCookieFromList(const nsListIter &aIter, mozIStorageBindingParamsArray *aParamsArray = NULL);
+    PRBool                        AddCookieToList(const nsCString& aBaseDomain, nsCookie *aCookie, mozIStorageBindingParamsArray *aParamsArray, PRBool aWriteToDB = PR_TRUE);
+    void                          UpdateCookieInList(nsCookie *aCookie, PRInt64 aLastAccessed, mozIStorageBindingParamsArray *aParamsArray);
     static PRBool                 GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, nsASingleFragmentCString::const_char_iterator &aEndIter, nsDependentCSubstring &aTokenString, nsDependentCSubstring &aTokenValue, PRBool &aEqualsFound);
     static PRBool                 ParseAttributes(nsDependentCString &aCookieHeader, nsCookieAttributes &aCookie);
     PRBool                        IsForeign(const nsCString &aBaseDomain, PRBool aRequireHostMatch, nsIURI *aFirstURI);
     PRUint32                      CheckPrefs(nsIURI *aHostURI, nsIChannel *aChannel, const nsCString &aBaseDomain, PRBool aRequireHostMatch, const char *aCookieHeader);
     PRBool                        CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, PRBool aRequireHostMatch);
     static PRBool                 CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static PRBool                 GetExpiry(nsCookieAttributes &aCookie, PRInt64 aServerTime, PRInt64 aCurrentTime);
     void                          RemoveAllFromMemory();
--- a/storage/public/storage.h
+++ b/storage/public/storage.h
@@ -38,29 +38,31 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_storage_h_
 #define mozilla_storage_h_
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Public Interfaces
 
+#include "mozStorageCID.h"
 #include "mozIStorageAggregateFunction.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageError.h"
 #include "mozIStorageFunction.h"
 #include "mozIStoragePendingStatement.h"
 #include "mozIStorageProgressHandler.h"
 #include "mozIStorageResultSet.h"
 #include "mozIStorageRow.h"
 #include "mozIStorageService.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageStatementCallback.h"
+#include "mozIStorageBindingParamsArray.h"
+#include "mozIStorageBindingParams.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Native Language Helpers
 
 #include "mozStorageHelper.h"
-#include "mozStorageCID.h"
 
 #include "mozilla/storage/Variant.h"
 
 #endif // mozilla_storage_h_