Bug 599969 - Do not use steps for async visit adding
authorShawn Wilsher <me@shawnwilsher.com>
Mon, 08 Nov 2010 11:45:46 -0800
changeset 59323 47cdf340a18236cf9fd1b5083f7c66c92fbcaab9
parent 59322 3b477671678ac5e2869ae5d36526a9f38b0aa2ce
child 59324 797ce3f26f043d30c12a97487f2ee2cb699d76ad
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
bugs599969
milestone2.0b8pre
Bug 599969 - Do not use steps for async visit adding Part 4 - use one event (ran on the background thread) for setting the page title, and one event (ran on the main thread) for notifying. r=mak
toolkit/components/places/src/History.cpp
toolkit/components/places/src/History.h
--- a/toolkit/components/places/src/History.cpp
+++ b/toolkit/components/places/src/History.cpp
@@ -38,21 +38,16 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifdef MOZ_IPC
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "nsXULAppAPI.h"
 #endif
 
-#ifdef MOZ_IPC
-#include "mozilla/dom/ContentChild.h"
-#include "nsXULAppAPI.h"
-#endif
-
 #include "History.h"
 #include "nsNavHistory.h"
 #include "nsNavBookmarks.h"
 #include "Helpers.h"
 
 #include "mozilla/storage.h"
 #include "mozilla/dom/Link.h"
 #include "nsDocShellCID.h"
@@ -69,97 +64,16 @@ namespace places {
 
 #define URI_VISITED "visited"
 #define URI_NOT_VISITED "not visited"
 #define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
 // Observer event fired after a visit has been registered in the DB.
 #define URI_VISIT_SAVED "uri-visit-saved"
 
 ////////////////////////////////////////////////////////////////////////////////
-//// Step
-
-class Step : public AsyncStatementCallback
-{
-public:
-  /**
-   * Executes statement asynchronously using this as a callback.
-   * 
-   * @param aStmt
-   *        Statement to execute asynchronously
-   */
-  NS_IMETHOD ExecuteAsync(mozIStorageStatement* aStmt);
-
-  /**
-   * Called once after query is completed.  If your query has more than one
-   * result set to process, you will want to override HandleResult to process
-   * each one.
-   *
-   * @param aResultSet
-   *        Results from ExecuteAsync
-   *        Unlike HandleResult, this *can be NULL* if there were no results.
-   */
-  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet);
-
-  /**
-   * By default, stores the last result set received in mResultSet.
-   * For queries with only one result set, you don't need to override.
-   *
-   * @param aResultSet
-   *        Results from ExecuteAsync
-   */
-  NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet);
-
-  /**
-   * By default, this calls Callback with any saved results from HandleResult.
-   * For queries with only one result set, you don't need to override.
-   *
-   * @param aReason
-   *        SQL status code
-   */
-  NS_IMETHOD HandleCompletion(PRUint16 aReason);
-
-private:
-  // Used by HandleResult to cache results until HandleCompletion is called.
-  nsCOMPtr<mozIStorageResultSet> mResultSet;
-};
-
-NS_IMETHODIMP
-Step::ExecuteAsync(mozIStorageStatement* aStmt)
-{
-  nsCOMPtr<mozIStoragePendingStatement> handle;
-  nsresult rv = aStmt->ExecuteAsync(this, getter_AddRefs(handle));
-  NS_ENSURE_SUCCESS(rv, rv);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-Step::Callback(mozIStorageResultSet* aResultSet)
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-Step::HandleResult(mozIStorageResultSet* aResultSet)
-{
-  mResultSet = aResultSet;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-Step::HandleCompletion(PRUint16 aReason)
-{
-  if (aReason == mozIStorageStatementCallback::REASON_FINISHED) {
-    nsCOMPtr<mozIStorageResultSet> resultSet = mResultSet;
-    mResultSet = NULL;
-    Callback(resultSet);
-  }
-  return NS_OK;
-}
-
-////////////////////////////////////////////////////////////////////////////////
 //// Anonymous Helpers
 
 namespace {
 
 class VisitedQuery : public AsyncStatementCallback
 {
 public:
   static nsresult Start(nsIURI* aURI)
@@ -295,16 +209,18 @@ struct VisitData {
 class NotifyVisitObservers : public nsRunnable
 {
 public:
   NotifyVisitObservers(VisitData& aPlace,
                        VisitData& aReferrer)
   : mPlace(aPlace)
   , mReferrer(aReferrer)
   {
+    NS_PRECONDITION(!NS_IsMainThread(),
+                    "This should not be called on the main thread");
   }
 
   NS_IMETHOD Run()
   {
     nsNavHistory* history = nsNavHistory::GetHistoryService();
     if (!history) {
       NS_WARNING("Trying to notify about a visit but cannot get the history service!");
       return NS_OK;
@@ -356,17 +272,16 @@ public:
                         VisitData& aPlace,
                         nsIURI* aReferrer = nsnull)
   {
     NS_PRECONDITION(NS_IsMainThread(),
                     "This should be called on the main thread");
 
     nsRefPtr<InsertVisitedURI> event =
       new InsertVisitedURI(aConnection, aPlace, aReferrer);
-    NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
 
     // Speculatively get a new session id for our visit.  While it is true that
     // we will use the session id from the referrer if the visit was "recent"
     // enough, we cannot call this method off of the main thread, so we have to
     // consume an id now.
     nsNavHistory* navhistory = nsNavHistory::GetHistoryService();
     NS_ENSURE_TRUE(navhistory, NS_ERROR_UNEXPECTED);
     event->mPlace.sessionId = navhistory->GetNewSessionID();
@@ -471,17 +386,16 @@ public:
     rv = UpdateFrecency(mPlace);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = transaction.Commit();
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Finally, dispatch an event to the main thread to notify observers.
     nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(mPlace, mReferrer);
-    NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
     rv = NS_DispatchToMainThread(event);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 private:
   InsertVisitedURI(mozIStorageConnection* aConnection,
                    VisitData& aPlace,
@@ -709,182 +623,221 @@ private:
   /**
    * Strong reference to the History object because we do not want it to
    * disappear out from under us.
    */
   nsRefPtr<History> mHistory;
 };
 
 /**
- * Fail-safe mechanism for ensuring that your task completes, no matter what.
- * Pass this around as an nsAutoPtr in your steps to guarantee that when all
- * your steps are finished, your task is finished.
- *
- * Be sure to use AppendTask to add your first step to the queue.
+ * Notifies observers about a pages title changing.
  */
-class FailSafeFinishTask
+class NotifyTitleObservers : public nsRunnable
 {
 public:
-  FailSafeFinishTask()
-  : mAppended(false)
+  /**
+   * Notifies observers on the main thread if we need to, and releases the
+   * URI (necessary to do on the main thread).
+   *
+   * @param aNotify
+   *        True if we should notify, false if not.
+   * @param aURI
+   *        Reference to the nsCOMPtr that owns the nsIURI object describing the
+   *        page we set the title on.  This will be null after this object is
+   *        constructed.
+   * @param aTitle
+   *        The new title to notify about.
+   */
+  NotifyTitleObservers(bool aNotify,
+                       nsCOMPtr<nsIURI>& aURI,
+                       const nsString& aTitle)
+  : mNotify(aNotify)
+  , mTitle(aTitle)
   {
-  }
+    NS_PRECONDITION(!NS_IsMainThread(),
+                    "This should not be called on the main thread");
 
-  ~FailSafeFinishTask()
-  {
-    if (mAppended) {
-      History::GetService()->CurrentTaskFinished();
-    }
+    // Do not want to AddRef and Release on the background thread!
+    mURI.swap(aURI);
   }
 
-  /**
-   * Appends task to History's queue.  When this object is destroyed, it will
-   * consider the task finished.
-   */
-  void AppendTask(Step* step)
+  NS_IMETHOD Run()
   {
-    History::GetService()->AppendTask(step);
-    mAppended = true;
-  }
-
-private:
-  bool mAppended;
-};
-////////////////////////////////////////////////////////////////////////////////
-//// Steps for SetURITitle
+    NS_PRECONDITION(NS_IsMainThread(),
+                    "This should be called on the main thread");
 
-struct SetTitleData : public FailSafeFinishTask
-{
-  nsCOMPtr<nsIURI> uri;
-  nsString title;
-};
+    if (!mNotify) {
+      return NS_OK;
+    }
 
-/**
- * Step 3: Notify that title has been updated.
- */
-class TitleNotifyStep: public Step
-{
-public:
-  TitleNotifyStep(nsAutoPtr<SetTitleData> aData)
-  : mData(aData)
-  {
-  }
-
-  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
-  {
-    nsNavHistory* history = nsNavHistory::GetHistoryService();
-    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
-    history->NotifyTitleChange(mData->uri, mData->title);
+    nsNavHistory* navhistory = nsNavHistory::GetHistoryService();
+    NS_ENSURE_TRUE(navhistory, NS_ERROR_OUT_OF_MEMORY);
+    navhistory->NotifyTitleChange(mURI, mTitle);
 
     return NS_OK;
   }
-
-protected:
-  nsAutoPtr<SetTitleData> mData;
+private:
+  const bool mNotify;
+  nsCOMPtr<nsIURI> mURI;
+  const nsString mTitle;
 };
 
+
 /**
- * Step 2: Set title.
+ * Sets the page title for a page in moz_places (if necessary).
  */
-class SetTitleStep : public Step
+class SetPageTitle : public nsRunnable
 {
 public:
-  SetTitleStep(nsAutoPtr<SetTitleData> aData)
-  : mData(aData)
-  {
-  }
-
-  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
+  /**
+   * Sets a pages title in the database asynchronously.
+   *
+   * @param aConnection
+   *        The database connection to use for this operation.
+   * @param aURI
+   *        The URI to set the page title on.
+   * @param aTitle
+   *        The title to set for the page, if the page exists.
+   */
+  static nsresult Start(mozIStorageConnection* aConnection,
+                        nsIURI* aURI,
+                        const nsString& aTitle)
   {
-    if (!aResultSet) {
-      // URI record was not found.
-      return NS_OK;
-    }
-
-    nsCOMPtr<mozIStorageRow> row;
-    nsresult rv = aResultSet->GetNextRow(getter_AddRefs(row));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsAutoString title;
-    rv = row->GetString(2, title);
-    NS_ENSURE_SUCCESS(rv, rv);
+    NS_PRECONDITION(NS_IsMainThread(),
+                    "This should be called on the main thread");
 
-    // It is actually common to set the title to be the same thing it used to
-    // be. For example, going to any web page will always cause a title to be set,
-    // even though it will often be unchanged since the last visit. In these
-    // cases, we can avoid DB writing and observer overhead.
-    if (mData->title.Equals(title) || (mData->title.IsVoid() && title.IsVoid()))
-      return NS_OK;
-
-    nsNavHistory* history = nsNavHistory::GetHistoryService();
-    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
-
-    nsCOMPtr<mozIStorageStatement> stmt =
-      history->GetStatementById(DB_SET_PLACE_TITLE);
-    NS_ENSURE_STATE(stmt);
+    nsRefPtr<SetPageTitle> event = new SetPageTitle(aConnection, aURI, aTitle);
 
-    if (mData->title.IsVoid()) {
-      rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
-    }
-    else {
-      rv = stmt->BindStringByName(
-        NS_LITERAL_CSTRING("page_title"),
-        StringHead(mData->title, TITLE_LENGTH_MAX)
-      );
-    }
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mData->uri);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<Step> step = new TitleNotifyStep(mData);
-    rv = step->ExecuteAsync(stmt);
+    // Get the target thread, and then start the work!
+    nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
+    NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+    nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
-protected:
-  nsAutoPtr<SetTitleData> mData;
-};
+  NS_IMETHOD Run()
+  {
+    NS_PRECONDITION(!NS_IsMainThread(),
+                    "This should not be called on the main thread");
 
-/**
- * Step 1: See if there is an existing URI.
- */
-class StartSetURITitleStep : public Step
-{
-public:
-  StartSetURITitleStep(nsAutoPtr<SetTitleData> aData)
-  : mData(aData)
-  {
-    mData->AppendTask(this);
-  }
-
-  NS_IMETHOD Callback(mozIStorageResultSet* aResultSet)
-  {
-    nsNavHistory* history = nsNavHistory::GetHistoryService();
-    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
-
-    // Find existing entry in moz_places table, if any.
+    // First, see if the page exists in the database (we'll need its id later).
     nsCOMPtr<mozIStorageStatement> stmt =
-      history->GetStatementById(DB_GET_URL_PAGE_INFO);
+      mHistory->syncStatements.GetCachedStatement(
+        "SELECT id, title "
+        "FROM moz_places "
+        "WHERE url = :page_url "
+      );
     NS_ENSURE_STATE(stmt);
 
-    nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mData->uri);
-    NS_ENSURE_SUCCESS(rv, rv);
+    PRInt64 placeId = 0;
+    nsAutoString title;
+    {
+      mozStorageStatementScoper scoper(stmt);
+      nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mURI);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      PRBool hasResult;
+      rv = stmt->ExecuteStep(&hasResult);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (!hasResult) {
+        // We have no record of this page, so there is no need to do any further
+        // work.
+        return Finish(false);
+      }
+
+      rv = stmt->GetInt64(0, &placeId);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = stmt->GetString(1, title);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    NS_ASSERTION(placeId > 0, "We somehow have an invalid place id here!");
 
-    nsCOMPtr<Step> step = new SetTitleStep(mData);
-    rv = step->ExecuteAsync(stmt);
+    // Also, if we have the same title, there is no reason to do another write
+    // or notify our observers, so bail early.
+    if (mTitle.Equals(title) || (mTitle.IsVoid() && title.IsVoid())) {
+      return Finish(false);
+    }
+
+    // Now we can update our database record.
+    stmt = mHistory->syncStatements.GetCachedStatement(
+        "UPDATE moz_places "
+        "SET title = :page_title "
+        "WHERE id = :page_id "
+      );
+    NS_ENSURE_STATE(stmt);
+
+    {
+      mozStorageStatementScoper scoper(stmt);
+      nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
+                                          placeId);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (mTitle.IsVoid()) {
+        rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
+      }
+      else {
+        rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
+                                    StringHead(mTitle, TITLE_LENGTH_MAX));
+      }
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = stmt->Execute();
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    nsresult rv = Finish(true);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
-protected:
-  nsAutoPtr<SetTitleData> mData;
+private:
+  SetPageTitle(mozIStorageConnection* aConnection,
+               nsIURI* aURI,
+               const nsString& aTitle)
+  : mDBConn(aConnection)
+  , mURI(aURI)
+  , mTitle(aTitle)
+  , mHistory(History::GetService())
+  {
+  }
+
+  /**
+   * Finishes our work by dispatching an event back to the main thread.
+   *
+   * @param aNotify
+   *        True if we should notify observers, false otherwise.
+   */
+  nsresult Finish(bool aNotify)
+  {
+    // We always dispatch this event because we have to release mURI on the
+    // main thread.
+    nsCOMPtr<nsIRunnable> event =
+      new NotifyTitleObservers(aNotify, mURI, mTitle);
+    nsresult rv = NS_DispatchToMainThread(event);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    NS_ASSERTION(!mURI,
+                 "We did not let go of our nsIURI reference after notifying!");
+
+    return NS_OK;
+  }
+
+  mozIStorageConnection* mDBConn;
+
+  nsCOMPtr<nsIURI> mURI;
+  const nsString mTitle;
+
+  /**
+   * Strong reference to the History object because we do not want it to
+   * disappear out from under us.
+   */
+  nsRefPtr<History> mHistory;
 };
 
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// History
 
 History* History::gService = NULL;
@@ -915,48 +868,16 @@ History::~History()
 #endif
 
   // Places shutdown event may not occur, but we *must* clean up before History
   // goes away.
   Shutdown();
 }
 
 void
-History::AppendTask(Step* aTask)
-{
-  NS_PRECONDITION(aTask, "Got NULL task.");
-
-  if (mShuttingDown) {
-    return;
-  }
-
-  NS_ADDREF(aTask);
-  mPendingVisits.Push(aTask);
-
-  if (mPendingVisits.GetSize() == 1) {
-    // There are no other pending tasks.
-    StartNextTask();
-  }
-}
-
-void
-History::CurrentTaskFinished()
-{
-  if (mShuttingDown) {
-    return;
-  }
-
-  NS_ASSERTION(mPendingVisits.PeekFront(), "Tried to finish task not on the queue");
-
-  nsCOMPtr<Step> deadTaskWalking =
-    dont_AddRef(static_cast<Step*>(mPendingVisits.PopFront()));
-  StartNextTask();
-}
-
-void
 History::NotifyVisited(nsIURI* aURI)
 {
   NS_ASSERTION(aURI, "Ruh-roh!  A NULL URI was passed to us!");
 
 #ifdef MOZ_IPC
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     mozilla::dom::ContentParent* cpp = 
       mozilla::dom::ContentParent::GetSingleton(PR_FALSE);
@@ -1059,42 +980,20 @@ History::GetDBConn()
 
   nsresult rv = history->GetDBConnection(getter_AddRefs(mDBConn));
   NS_ENSURE_SUCCESS(rv, nsnull);
 
   return mDBConn;
 }
 
 void
-History::StartNextTask()
-{
-  if (mShuttingDown) {
-    return;
-  }
-
-  nsCOMPtr<Step> nextTask =
-    static_cast<Step*>(mPendingVisits.PeekFront());
-  if (!nextTask) {
-    // No more pending visits left to process.
-    return;
-  }
-  nsresult rv = nextTask->Callback(NULL);
-  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Beginning a task failed.");
-}
-
-void
 History::Shutdown()
 {
   mShuttingDown = true;
 
-  while (mPendingVisits.PeekFront()) {
-    nsCOMPtr<Step> deadTaskWalking =
-      dont_AddRef(static_cast<Step*>(mPendingVisits.PopFront()));
-  }
-
   // Clean up our statements and connection.
   syncStatements.FinalizeStatements();
 
   if (mReadOnlyDBConn) {
     if (mIsVisitedStatement) {
       (void)mIsVisitedStatement->Finalize();
     }
     (void)mReadOnlyDBConn->AsyncClose(nsnull);
@@ -1335,29 +1234,29 @@ History::SetURITitle(nsIURI* aURI, const
 
   PRBool canAdd;
   nsresult rv = history->CanAddURI(aURI, &canAdd);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!canAdd) {
     return NS_OK;
   }
 
-  nsAutoPtr<SetTitleData> data(new SetTitleData());
-  NS_ENSURE_STATE(data);
-
-  data->uri = aURI;
-
+  nsAutoString title;
   if (aTitle.IsEmpty()) {
-    data->title.SetIsVoid(PR_TRUE);
+    title.SetIsVoid(PR_TRUE);
   }
   else {
-    data->title.Assign(aTitle);
+    title.Assign(aTitle);
   }
 
-  nsCOMPtr<Step> task(new StartSetURITitleStep(data));
+  mozIStorageConnection* dbConn = GetDBConn();
+  NS_ENSURE_STATE(dbConn);
+
+  rv = SetPageTitle::Start(dbConn, aURI, title);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIObserver
 
 NS_IMETHODIMP
--- a/toolkit/components/places/src/History.h
+++ b/toolkit/components/places/src/History.h
@@ -71,36 +71,16 @@ public:
    * Notifies about the visited status of a given URI.
    *
    * @param aURI
    *        The URI to notify about.
    */
   void NotifyVisited(nsIURI* aURI);
 
   /**
-   * Append a task to the queue for SQL queries that need to happen
-   * atomically.
-   *
-   * @pre aTask is not null
-   *
-   * @param aTask
-   *        Task that needs to be completed atomically
-   */
-  void AppendTask(class Step* aTask);
-
-  /**
-   * Call when all steps of the current running task are finished.  Each task
-   * should be responsible for calling this when it is finished (even if there
-   * are errors).
-   *
-   * Do not call this twice for the same visit.
-   */
-  void CurrentTaskFinished();
-
-  /**
    * Obtains the statement to use to check if a URI is visited or not.
    */
   mozIStorageAsyncStatement* GetIsVisitedStatement();
 
   /**
    * Obtains a pointer to this service.
    */
   static History* GetService();
@@ -143,36 +123,16 @@ private:
   /**
    * 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;
 
   /**
-   * Since visits rapidly fire at once, it's very likely to have race
-   * conditions for SQL queries.  We often need to see if a row exists
-   * or peek at values, and by the time we have retrieved them they could
-   * be different.
-   *
-   * We guarantee an ordering of our SQL statements so that a set of
-   * callbacks for one visit are guaranteed to be atomic.  Each visit consists
-   * of a data structure that sits in this queue.
-   *
-   * The front of the queue always has the current visit we are processing.
-   */
-  nsDeque mPendingVisits;
-
-  /**
-   * Begins next task at the front of the queue.  The task remains in the queue
-   * until it is done and calls CurrentTaskFinished.
-   */
-  void StartNextTask();
-
-  /**
    * Remove any memory references to tasks and do not take on any more.
    */
   void Shutdown();
 
   static History* gService;
 
   // Ensures new tasks aren't started on destruction.
   bool mShuttingDown;