Bug 887865 - Use a mozIStorageAsyncConnection in for GetIsVisitedStatement. r=mano
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 12 Aug 2014 10:59:11 +0200
changeset 220695 b3877ffb2c4a794485a5b49b1f7f9ba9be386310
parent 220694 fb046105fc73011113ebc7a43ecd0a86caef1228
child 220696 4821747ca37df9958afac3a22ed6aec164fdb020
push idunknown
push userunknown
push dateunknown
reviewersmano
bugs887865
milestone34.0a1
Bug 887865 - Use a mozIStorageAsyncConnection in for GetIsVisitedStatement. r=mano
toolkit/components/places/History.cpp
toolkit/components/places/History.h
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -425,19 +425,22 @@ GetJSObjectFromArray(JSContext* aCtx,
   JS::Rooted<JS::Value> value(aCtx);
   bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
   NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
   NS_ENSURE_ARG(!value.isPrimitive());
   objOut.set(&value.toObject());
   return NS_OK;
 }
 
-class VisitedQuery : public AsyncStatementCallback
+class VisitedQuery MOZ_FINAL: public AsyncStatementCallback,
+                              public mozIStorageCompletionCallback
 {
 public:
+  NS_DECL_ISUPPORTS_INHERITED
+
   static nsresult Start(nsIURI* aURI,
                         mozIVisitedStatusCallback* aCallback=nullptr)
   {
     NS_PRECONDITION(aURI, "Null URI");
 
   // If we are a content process, always remote the request to the
   // parent process.
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
@@ -461,28 +464,37 @@ public:
         NS_NewRunnableMethod(callback, &VisitedQuery::NotifyVisitedStatus);
       NS_DispatchToMainThread(event);
 
       return NS_OK;
     }
 
     History* history = History::GetService();
     NS_ENSURE_STATE(history);
-    mozIStorageAsyncStatement* stmt = history->GetIsVisitedStatement();
-    NS_ENSURE_STATE(stmt);
-
-    // Bind by index for performance.
-    nsresult rv = URIBinder::Bind(stmt, 0, aURI);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback);
     NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
+    nsresult rv = history->GetIsVisitedStatement(callback);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  // Note: the return value matters here.  We call into this method, it's not
+  // just xpcom boilerplate.
+  NS_IMETHOD Complete(nsresult aResult, nsISupports* aStatement)
+  {
+    NS_ENSURE_SUCCESS(aResult, aResult);
+    nsCOMPtr<mozIStorageAsyncStatement> stmt = do_QueryInterface(aStatement);
+    NS_ENSURE_STATE(stmt);
+    // Bind by index for performance.
+    nsresult rv = URIBinder::Bind(stmt, 0, mURI);
+    NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<mozIStoragePendingStatement> handle;
-    return stmt->ExecuteAsync(callback, getter_AddRefs(handle));
+    return stmt->ExecuteAsync(this, getter_AddRefs(handle));
   }
 
   NS_IMETHOD HandleResult(mozIStorageResultSet* aResults)
   {
     // If this method is called, we've gotten results, which means we have a
     // visit.
     mIsVisited = true;
     return NS_OK;
@@ -543,21 +555,31 @@ private:
                mozIVisitedStatusCallback *aCallback=nullptr,
                bool aIsVisited=false)
   : mURI(aURI)
   , mCallback(aCallback)
   , mIsVisited(aIsVisited)
   {
   }
 
+  ~VisitedQuery()
+  {
+  }
+
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<mozIVisitedStatusCallback> mCallback;
   bool mIsVisited;
 };
 
+NS_IMPL_ISUPPORTS_INHERITED(
+  VisitedQuery
+, AsyncStatementCallback
+, mozIStorageCompletionCallback
+)
+
 /**
  * Notifies observers about a visit.
  */
 class NotifyVisitObservers : public nsRunnable
 {
 public:
   NotifyVisitObservers(VisitData& aPlace,
                        VisitData& aReferrer)
@@ -1998,41 +2020,105 @@ History::NotifyVisited(nsIURI* aURI)
     }
   }
 
   // All the registered nodes can now be removed for this URI.
   mObservers.RemoveEntry(aURI);
   return NS_OK;
 }
 
-mozIStorageAsyncStatement*
-History::GetIsVisitedStatement()
+class ConcurrentStatementsHolder MOZ_FINAL : public mozIStorageCompletionCallback {
+public:
+  NS_DECL_ISUPPORTS
+
+  ConcurrentStatementsHolder(mozIStorageConnection* aDBConn)
+  {
+    DebugOnly<nsresult> rv = aDBConn->AsyncClone(true, this);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+
+  NS_IMETHOD Complete(nsresult aStatus, nsISupports* aConnection) {
+    if (NS_FAILED(aStatus))
+      return NS_OK;
+    mReadOnlyDBConn = do_QueryInterface(aConnection);
+
+    // Now we can create our cached statements.
+
+    if (!mIsVisitedStatement) {
+      (void)mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+        "SELECT 1 FROM moz_places h "
+        "WHERE url = ?1 AND last_visit_date NOTNULL "
+      ),  getter_AddRefs(mIsVisitedStatement));
+      MOZ_ASSERT(mIsVisitedStatement);
+      nsresult result = mIsVisitedStatement ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+      for (int32_t i = 0; i < mIsVisitedCallbacks.Count(); ++i) {
+        DebugOnly<nsresult> rv;
+        rv = mIsVisitedCallbacks[i]->Complete(result, mIsVisitedStatement);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+      }
+      mIsVisitedCallbacks.Clear();
+    }
+
+    return NS_OK;
+  }
+
+  void GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback)
+  {
+    if (mIsVisitedStatement) {
+      DebugOnly<nsresult> rv;
+      rv = aCallback->Complete(NS_OK, mIsVisitedStatement);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    } else {
+      DebugOnly<bool> added = mIsVisitedCallbacks.AppendObject(aCallback);
+      MOZ_ASSERT(added);
+    }
+  }
+
+  void Shutdown() {
+    if (mReadOnlyDBConn) {
+      mIsVisitedCallbacks.Clear();
+      DebugOnly<nsresult> rv;
+      if (mIsVisitedStatement) {
+        rv = mIsVisitedStatement->Finalize();
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+      }
+      rv = mReadOnlyDBConn->AsyncClose(nullptr);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+  }
+
+private:
+  ~ConcurrentStatementsHolder()
+  {
+  }
+
+  nsCOMPtr<mozIStorageAsyncConnection> mReadOnlyDBConn;
+  nsCOMPtr<mozIStorageAsyncStatement> mIsVisitedStatement;
+  nsCOMArray<mozIStorageCompletionCallback> mIsVisitedCallbacks;
+};
+
+NS_IMPL_ISUPPORTS(
+  ConcurrentStatementsHolder
+, mozIStorageCompletionCallback
+)
+
+nsresult
+History::GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback)
 {
-  if (mIsVisitedStatement) {
-    return mIsVisitedStatement;
-  }
-
-  // If we don't yet have a database connection, go ahead and clone it now.
-  if (!mReadOnlyDBConn) {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mShuttingDown)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  if (!mConcurrentStatementsHolder) {
     mozIStorageConnection* dbConn = GetDBConn();
-    NS_ENSURE_TRUE(dbConn, nullptr);
-
-    (void)dbConn->Clone(true, getter_AddRefs(mReadOnlyDBConn));
-    NS_ENSURE_TRUE(mReadOnlyDBConn, nullptr);
+    NS_ENSURE_STATE(dbConn);
+    mConcurrentStatementsHolder = new ConcurrentStatementsHolder(dbConn);
   }
-
-  // Now we can create our cached statement.
-  nsresult rv = mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
-    "SELECT 1 "
-    "FROM moz_places h "
-    "WHERE url = ?1 "
-      "AND last_visit_date NOTNULL "
-  ),  getter_AddRefs(mIsVisitedStatement));
-  NS_ENSURE_SUCCESS(rv, nullptr);
-  return mIsVisitedStatement;
+  mConcurrentStatementsHolder->GetIsVisitedStatement(aCallback);
+  return NS_OK;
 }
 
 nsresult
 History::InsertPlace(const VisitData& aPlace)
 {
   NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!");
   NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
 
@@ -2278,16 +2364,18 @@ History::GetSingleton()
 
   NS_ADDREF(gService);
   return gService;
 }
 
 mozIStorageConnection*
 History::GetDBConn()
 {
+  if (mShuttingDown)
+    return nullptr;
   if (!mDB) {
     mDB = Database::GetDatabase();
     NS_ENSURE_TRUE(mDB, nullptr);
   }
   return mDB->MainConn();
 }
 
 void
@@ -2297,21 +2385,18 @@ History::Shutdown()
 
   // Prevent other threads from scheduling uses of the DB while we mark
   // ourselves as shutting down.
   MutexAutoLock lockedScope(mShutdownMutex);
   MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!");
 
   mShuttingDown = true;
 
-  if (mReadOnlyDBConn) {
-    if (mIsVisitedStatement) {
-      (void)mIsVisitedStatement->Finalize();
-    }
-    (void)mReadOnlyDBConn->AsyncClose(nullptr);
+  if (mConcurrentStatementsHolder) {
+    mConcurrentStatementsHolder->Shutdown();
   }
 }
 
 void
 History::AppendToRecentlyVisitedURIs(nsIURI* aURI) {
   if (mRecentlyVisitedURIs.Length() < RECENTLY_VISITED_URI_SIZE) {
     // Append a new element while the array is not full.
     mRecentlyVisitedURIs.AppendElement(aURI);
--- a/toolkit/components/places/History.h
+++ b/toolkit/components/places/History.h
@@ -23,16 +23,17 @@
 #include "nsIMemoryReporter.h"
 #include "nsIObserver.h"
 #include "mozIStorageConnection.h"
 
 namespace mozilla {
 namespace places {
 
 struct VisitData;
+class ConcurrentStatementsHolder;
 
 #define NS_HISTORYSERVICE_CID \
   {0x0937a705, 0x91a6, 0x417a, {0x82, 0x92, 0xb2, 0x2e, 0xb1, 0x0d, 0xa8, 0x6c}}
 
 // Max size of History::mRecentlyVisitedURIs
 #define RECENTLY_VISITED_URI_SIZE 8
 
 class History : public IHistory
@@ -49,17 +50,17 @@ public:
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIMEMORYREPORTER
 
   History();
 
   /**
    * Obtains the statement to use to check if a URI is visited or not.
    */
-  mozIStorageAsyncStatement* GetIsVisitedStatement();
+  nsresult GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback);
 
   /**
    * Adds an entry in moz_places with the data in aVisitData.
    *
    * @param aVisitData
    *        The visit data to use to populate a new row in moz_places.
    */
   nsresult InsertPlace(const VisitData& aVisitData);
@@ -141,29 +142,17 @@ private:
 
   /**
    * The database handle.  This is initialized lazily by the first call to
    * GetDBConn(), so never use it directly, or, if you really need, always
    * invoke GetDBConn() before.
    */
   nsRefPtr<mozilla::places::Database> mDB;
 
-  /**
-   * A read-only database connection used for checking if a URI is visited.
-   *
-   * @note this should only be accessed by GetIsVisistedStatement and Shutdown.
-   */
-  nsCOMPtr<mozIStorageConnection> mReadOnlyDBConn;
-
-  /**
-   * An asynchronous statement to query if a URI is visited or not.
-   *
-   * @note this should only be accessed by GetIsVisistedStatement and Shutdown.
-   */
-  nsCOMPtr<mozIStorageAsyncStatement> mIsVisitedStatement;
+  nsRefPtr<ConcurrentStatementsHolder> mConcurrentStatementsHolder;
 
   /**
    * Remove any memory references to tasks and do not take on any more.
    */
   void Shutdown();
 
   static History* gService;