Bug 695003 - Race condition when deleting cache directory
authorMichal Novotny <michal.novotny@gmail.com>
Fri, 02 Dec 2011 18:10:57 +0100
changeset 81994 bb764828a379ddf26e1952da10b530a2db990c9a
parent 81993 e6d311cfc2e43f4a8b4fb096c00b45688a2415d9
child 81995 8d5aa2c53f04e610866b08f9c9860a6ae1891394
push idunknown
push userunknown
push dateunknown
bugs695003
milestone11.0a1
Bug 695003 - Race condition when deleting cache directory
netwerk/cache/nsCacheService.cpp
netwerk/cache/nsDeleteDir.cpp
netwerk/cache/nsDeleteDir.h
netwerk/cache/nsDiskCacheDevice.cpp
toolkit/components/telemetry/TelemetryHistograms.h
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -692,17 +692,17 @@ nsCacheProfilePrefObserver::ReadPrefs(ns
                 bool same;
                 if (NS_SUCCEEDED(profDir->Equals(directory, &same)) && !same) {
                     // We no longer store the cache directory in the main
                     // profile directory, so we should cleanup the old one.
                     rv = profDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
                     if (NS_SUCCEEDED(rv)) {
                         bool exists;
                         if (NS_SUCCEEDED(profDir->Exists(&exists)) && exists)
-                            DeleteDir(profDir, false, false);
+                            nsDeleteDir::DeleteDir(profDir, false);
                     }
                 }
             }
         }
         // use file cache in build tree only if asked, to avoid cache dir litter
         if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
             rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
                                         getter_AddRefs(directory));
@@ -1033,16 +1033,21 @@ nsCacheService::Init()
 
     CACHE_LOG_INIT();
 
     nsresult rv = NS_NewThread(getter_AddRefs(mCacheIOThread));
     if (NS_FAILED(rv)) {
         NS_WARNING("Can't create cache IO thread");
     }
 
+    rv = nsDeleteDir::Init();
+    if (NS_FAILED(rv)) {
+        NS_WARNING("Can't initialize nsDeleteDir");
+    }
+
     // initialize hashtable for active cache entries
     rv = mActiveEntries.Init();
     if (NS_FAILED(rv)) return rv;
     
     // create profile/preference observer
     mObserver = new nsCacheProfilePrefObserver();
     if (!mObserver)  return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(mObserver);
@@ -1056,16 +1061,17 @@ nsCacheService::Init()
     return NS_OK;
 }
 
 
 void
 nsCacheService::Shutdown()
 {
     nsCOMPtr<nsIThread> cacheIOThread;
+    Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
 
     {
     nsCacheServiceAutoLock lock;
     NS_ASSERTION(mInitialized, 
                  "can't shutdown nsCacheService unless it has been initialized.");
 
     if (mInitialized) {
 
@@ -1101,16 +1107,39 @@ nsCacheService::Shutdown()
 #endif
 
         mCacheIOThread.swap(cacheIOThread);
     }
     } // lock
 
     if (cacheIOThread)
         cacheIOThread->Shutdown();
+
+    bool finishDeleting = false;
+    nsresult rv;
+    nsCOMPtr<nsIPrefBranch2> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+    if (!branch) {
+        NS_WARNING("Failed to get pref service!");
+    } else {
+        bool isSet;
+        rv = branch->GetBoolPref("privacy.sanitize.sanitizeOnShutdown", &isSet);
+        if (NS_SUCCEEDED(rv) && isSet) {
+            rv = branch->GetBoolPref("privacy.clearOnShutdown.cache", &isSet);
+            if (NS_SUCCEEDED(rv) && isSet) {
+                finishDeleting = true;
+            }
+        }
+    }
+    if (finishDeleting) {
+      Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer;
+      nsDeleteDir::Shutdown(finishDeleting);
+    } else {
+      Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer;
+      nsDeleteDir::Shutdown(finishDeleting);
+    }
 }
 
 
 nsresult
 nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
 {
     nsresult  rv;
 
--- a/netwerk/cache/nsDeleteDir.cpp
+++ b/netwerk/cache/nsDeleteDir.cpp
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is Google Inc.
  * Portions created by the Initial Developer are Copyright (C) 2005
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Darin Fisher <darin@meer.net>
  *  Jason Duell <jduell.mcbugs@gmail.com>
+ *  Michal Novotny <michal.novotny@gmail.com>
  *
  * 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
@@ -35,110 +36,427 @@
  * 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 "nsDeleteDir.h"
 #include "nsIFile.h"
 #include "nsString.h"
-#include "prthread.h"
 #include "mozilla/Telemetry.h"
 #include "nsITimer.h"
+#include "nsISimpleEnumerator.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "nsISupportsPriority.h"
 
 using namespace mozilla;
 
-static void DeleteDirThreadFunc(void *arg)
+class nsBlockOnBackgroundThreadEvent : public nsRunnable {
+public:
+  nsBlockOnBackgroundThreadEvent() {}
+  NS_IMETHOD Run()
+  {
+    MutexAutoLock lock(nsDeleteDir::gInstance->mLock);
+    nsDeleteDir::gInstance->mCondVar.Notify();
+    return NS_OK;
+  }
+};
+
+class nsDestroyThreadEvent : public nsRunnable {
+public:
+  nsDestroyThreadEvent(nsIThread *thread)
+    : mThread(thread)
+  {}
+  NS_IMETHOD Run()
+  {
+    mThread->Shutdown();
+    return NS_OK;
+  }
+private:
+  nsCOMPtr<nsIThread> mThread;
+};
+
+
+nsDeleteDir * nsDeleteDir::gInstance = nsnull;
+
+nsDeleteDir::nsDeleteDir()
+  : mLock("nsDeleteDir.mLock"),
+    mCondVar(mLock, "nsDeleteDir.mCondVar"),
+    mShutdownPending(false),
+    mStopDeleting(false)
 {
-  Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer;
-  nsIFile *dir = static_cast<nsIFile *>(arg);
-  dir->Remove(true);
-  NS_RELEASE(dir);
+  NS_ASSERTION(gInstance==nsnull, "multiple nsCacheService instances!");
+}
+
+nsDeleteDir::~nsDeleteDir()
+{
+  gInstance = nsnull;
+}
+
+nsresult
+nsDeleteDir::Init()
+{
+  if (gInstance)
+    return NS_ERROR_ALREADY_INITIALIZED;
+
+  gInstance = new nsDeleteDir();
+  return NS_OK;
 }
 
-static void CreateDeleterThread(nsITimer *aTimer, void *arg)
+nsresult
+nsDeleteDir::Shutdown(bool finishDeleting)
 {
-  nsIFile *dir = static_cast<nsIFile *>(arg);
+  if (!gInstance)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsCOMArray<nsIFile> dirsToRemove;
+  nsCOMPtr<nsIThread> thread;
+  {
+    MutexAutoLock lock(gInstance->mLock);
+    NS_ASSERTION(!gInstance->mShutdownPending,
+                 "Unexpected state in nsDeleteDir::Shutdown()");
+    gInstance->mShutdownPending = true;
+
+    if (!finishDeleting)
+      gInstance->mStopDeleting = true;
+
+    // remove all pending timers
+    for (PRInt32 i = gInstance->mTimers.Count(); i > 0; i--) {
+      nsCOMPtr<nsITimer> timer = gInstance->mTimers[i-1];
+      gInstance->mTimers.RemoveObjectAt(i-1);
+      timer->Cancel();
+
+      nsCOMArray<nsIFile> *arg;
+      timer->GetClosure((reinterpret_cast<void**>(&arg)));
 
-  // create the worker thread
-  PR_CreateThread(PR_USER_THREAD, DeleteDirThreadFunc, dir, PR_PRIORITY_LOW,
-                  PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0);
+      if (finishDeleting)
+        dirsToRemove.AppendObjects(*arg);
+
+      // delete argument passed to the timer
+      delete arg;
+    }
+
+    thread.swap(gInstance->mThread);
+    if (thread) {
+      // dispatch event and wait for it to run and notify us, so we know thread
+      // has completed all work and can be shutdown
+      nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent();
+      nsresult rv = thread->Dispatch(event, NS_DISPATCH_NORMAL);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed dispatching block-event");
+        return NS_ERROR_UNEXPECTED;
+      }
+
+      rv = gInstance->mCondVar.Wait();
+      thread->Shutdown();
+    }
+  }
+
+  delete gInstance;
+
+  for (PRInt32 i = 0; i < dirsToRemove.Count(); i++)
+    dirsToRemove[i]->Remove(true);
+
+  return NS_OK;
 }
 
-nsresult DeleteDir(nsIFile *dirIn, bool moveToTrash, bool sync,
-                   PRUint32 delay)
+nsresult
+nsDeleteDir::InitThread()
+{
+  if (mThread)
+    return NS_OK;
+
+  nsresult rv = NS_NewThread(getter_AddRefs(mThread));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Can't create background thread");
+    return rv;
+  }
+
+  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread);
+  if (p) {
+    p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
+  }
+  return NS_OK;
+}
+
+void
+nsDeleteDir::DestroyThread()
+{
+  if (!mThread)
+    return;
+
+  if (mTimers.Count())
+    // more work to do, so don't delete thread.
+    return;
+
+  NS_DispatchToMainThread(new nsDestroyThreadEvent(mThread));
+  mThread = nsnull;
+}
+
+void
+nsDeleteDir::TimerCallback(nsITimer *aTimer, void *arg)
+{
+  Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer;
+  {
+    MutexAutoLock lock(gInstance->mLock);
+
+    PRInt32 idx = gInstance->mTimers.IndexOf(aTimer);
+    if (idx == -1) {
+      // Timer was canceled and removed during shutdown.
+      return;
+    }
+
+    gInstance->mTimers.RemoveObjectAt(idx);
+  }
+
+  nsAutoPtr<nsCOMArray<nsIFile> > dirList;
+  dirList = static_cast<nsCOMArray<nsIFile> *>(arg);
+
+  bool shuttingDown = false;
+  for (PRInt32 i = 0; i < dirList->Count() && !shuttingDown; i++) {
+    gInstance->RemoveDir((*dirList)[i], &shuttingDown);
+  }
+
+  {
+    MutexAutoLock lock(gInstance->mLock);
+    gInstance->DestroyThread();
+  }
+}
+
+nsresult
+nsDeleteDir::DeleteDir(nsIFile *dirIn, bool moveToTrash, PRUint32 delay)
 {
   Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer;
+
+  if (!gInstance)
+    return NS_ERROR_NOT_INITIALIZED;
+
   nsresult rv;
   nsCOMPtr<nsIFile> trash, dir;
 
   // Need to make a clone of this since we don't want to modify the input
   // file object.
   rv = dirIn->Clone(getter_AddRefs(dir));
   if (NS_FAILED(rv))
     return rv;
 
   if (moveToTrash) {
     rv = GetTrashDir(dir, &trash);
     if (NS_FAILED(rv))
       return rv;
-    nsCAutoString leaf;
-    rv = trash->GetNativeLeafName(leaf);
+    nsCAutoString origLeaf;
+    rv = trash->GetNativeLeafName(origLeaf);
     if (NS_FAILED(rv))
       return rv;
 
     // Important: must rename directory w/o changing parent directory: else on
     // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file
     // tree: was hanging GUI for *minutes* on large cache dirs.
+    // Append random number to the trash directory and check if it exists.
+    nsCAutoString leaf;
+    for (PRInt32 i = 0; i < 10; i++) {
+      leaf = origLeaf;
+      leaf.AppendInt(rand());
+      rv = trash->SetNativeLeafName(leaf);
+      if (NS_FAILED(rv))
+        return rv;
+
+      bool exists;
+      if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
+        break;
+      }
+
+      leaf.Truncate();
+    }
+
+    // Fail if we didn't find unused trash directory within the limit
+    if (!leaf.Length())
+      return NS_ERROR_FAILURE;
+
     rv = dir->MoveToNative(nsnull, leaf);
-    if (NS_FAILED(rv)) {
-      nsresult rvMove = rv;
-      // TrashDir may already exist (if we crashed while deleting it, etc.)
-      // In that case current Cache dir should be small--just move it to
-      // subdirectory of TrashDir and eat the NTFS ACL overhead.
-      leaf.AppendInt(rand()); // support this happening multiple times
-      rv = dir->MoveToNative(trash, leaf);
-      if (NS_FAILED(rv))
-        return rvMove;
-      // Be paranoid and delete immediately if we're seeing old trash when
-      // we're creating a new one
-      delay = 0;
-    }
+    if (NS_FAILED(rv))
+      return rv;
   } else {
     // we want to pass a clone of the original off to the worker thread.
     trash.swap(dir);
   }
 
-  // Steal ownership of trash directory; let the thread release it.
-  nsIFile *trashRef = nsnull;
-  trash.swap(trashRef);
+  nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>);
+  arg->AppendObject(trash);
 
-  if (sync) {
-    DeleteDirThreadFunc(trashRef);
-  } else {
-    if (delay) {
-      nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
-      if (NS_FAILED(rv))
-        return NS_ERROR_UNEXPECTED;
-      timer->InitWithFuncCallback(CreateDeleterThread, trashRef, delay,
-                                  nsITimer::TYPE_ONE_SHOT);
-    } else {
-      CreateDeleterThread(nsnull, trashRef);
-    }
-  }
+  rv = gInstance->PostTimer(arg, delay);
+  if (NS_FAILED(rv))
+    return rv;
 
+  arg.forget();
   return NS_OK;
 }
 
-nsresult GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result)
+nsresult
+nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result)
 {
   nsresult rv = target->Clone(getter_AddRefs(*result));
   if (NS_FAILED(rv))
     return rv;
 
   nsCAutoString leaf;
   rv = (*result)->GetNativeLeafName(leaf);
   if (NS_FAILED(rv))
     return rv;
   leaf.AppendLiteral(".Trash");
 
   return (*result)->SetNativeLeafName(leaf);
 }
+
+nsresult
+nsDeleteDir::RemoveOldTrashes(nsIFile *cacheDir)
+{
+  if (!gInstance)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  nsresult rv;
+
+  static bool firstRun = true;
+
+  if (!firstRun)
+    return NS_OK;
+
+  firstRun = false;
+
+  nsCOMPtr<nsIFile> trash;
+  rv = GetTrashDir(cacheDir, &trash);
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsAutoString trashName;
+  rv = trash->GetLeafName(trashName);
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsIFile> parent;
+  rv = cacheDir->GetParent(getter_AddRefs(parent));
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsISimpleEnumerator> iter;
+  rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
+  if (NS_FAILED(rv))
+    return rv;
+
+  bool more;
+  nsCOMPtr<nsISupports> elem;
+  nsAutoPtr<nsCOMArray<nsIFile> > dirList;
+
+  while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+    rv = iter->GetNext(getter_AddRefs(elem));
+    if (NS_FAILED(rv))
+      continue;
+
+    nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
+    if (!file)
+      continue;
+
+    nsAutoString leafName;
+    rv = file->GetLeafName(leafName);
+    if (NS_FAILED(rv))
+      continue;
+
+    // match all names that begin with the trash name (i.e. "Cache.Trash")
+    if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) {
+      if (!dirList)
+        dirList = new nsCOMArray<nsIFile>;
+      dirList->AppendObject(file);
+    }
+  }
+
+  if (dirList) {
+    rv = gInstance->PostTimer(dirList, 90000);
+    if (NS_FAILED(rv))
+      return rv;
+
+    dirList.forget();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsDeleteDir::PostTimer(void *arg, PRUint32 delay)
+{
+  nsresult rv;
+
+  nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+  if (NS_FAILED(rv))
+    return NS_ERROR_UNEXPECTED;
+
+  MutexAutoLock lock(mLock);
+
+  rv = InitThread();
+  if (NS_FAILED(rv))
+    return rv;
+
+  rv = timer->SetTarget(mThread);
+  if (NS_FAILED(rv))
+    return rv;
+
+  rv = timer->InitWithFuncCallback(TimerCallback, arg, delay,
+                                   nsITimer::TYPE_ONE_SHOT);
+  if (NS_FAILED(rv))
+    return rv;
+
+  mTimers.AppendObject(timer);
+  return NS_OK;
+}
+
+nsresult
+nsDeleteDir::RemoveDir(nsIFile *file, bool *stopDeleting)
+{
+  nsresult rv;
+  bool isLink;
+
+  rv = file->IsSymlink(&isLink);
+  if (NS_FAILED(rv) || isLink)
+    return NS_ERROR_UNEXPECTED;
+
+  bool isDir;
+  rv = file->IsDirectory(&isDir);
+  if (NS_FAILED(rv))
+    return rv;
+
+  if (isDir) {
+    nsCOMPtr<nsISimpleEnumerator> iter;
+    rv = file->GetDirectoryEntries(getter_AddRefs(iter));
+    if (NS_FAILED(rv))
+      return rv;
+
+    bool more;
+    nsCOMPtr<nsISupports> elem;
+    while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
+      rv = iter->GetNext(getter_AddRefs(elem));
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
+        continue;
+      }
+
+      nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem);
+      if (!file2) {
+        NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
+        continue;
+      }
+
+      RemoveDir(file2, stopDeleting);
+      // No check for errors to remove as much as possible
+
+      if (*stopDeleting)
+        return NS_OK;
+    }
+  }
+
+  file->Remove(false);
+  // No check for errors to remove as much as possible
+
+  MutexAutoLock lock(mLock);
+  if (mStopDeleting)
+    *stopDeleting = true;
+
+  return NS_OK;
+}
--- a/netwerk/cache/nsDeleteDir.h
+++ b/netwerk/cache/nsDeleteDir.h
@@ -16,16 +16,17 @@
  * The Original Code is mozilla.org code.
  *
  * The Initial Developer of the Original Code is Google Inc.
  * Portions created by the Initial Developer are Copyright (C) 2005
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Darin Fisher <darin@meer.net>
+ *  Michal Novotny <michal.novotny@gmail.com>
  *
  * 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
@@ -35,44 +36,77 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsDeleteDir_h__
 #define nsDeleteDir_h__
 
 #include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
 
 class nsIFile;
+class nsIThread;
+class nsITimer;
 
-/**
- * This routine attempts to delete a directory that may contain some files that
- * are still in use.  This later point is only an issue on Windows and a few
- * other systems.
- *
- * If the moveToTrash parameter is true, then the process for deleting the
- * directory creates a sibling directory of the same name with the ".Trash"
- * suffix.  It then attempts to move the given directory into the corresponding
- * trash folder (moving individual files if necessary).  Next, it proceeds to
- * delete each file in the trash folder on a low-priority background thread.
- *
- * If the moveToTrash parameter is false, then the given directory is deleted
- * directly.
- *
- * If the sync flag is true, then the delete operation runs to completion
- * before this function returns.  Otherwise, deletion occurs asynchronously.
- *
- * If 'delay' is non-zero, the directory will not be deleted until the
- * specified number of milliseconds have passed. (The directory is still
- * renamed immediately if 'moveToTrash' is passed, so upon return it is safe
- * to create a directory with the same name). This parameter is ignored if
- * 'sync' is true.
- */
-NS_HIDDEN_(nsresult) DeleteDir(nsIFile *dir, bool moveToTrash, bool sync, 
-                               PRUint32 delay = 0);
+
+class nsDeleteDir {
+public:
+  nsDeleteDir();
+  ~nsDeleteDir();
+
+  static nsresult Init();
+  static nsresult Shutdown(bool finishDeleting);
+
+  /**
+   * This routine attempts to delete a directory that may contain some files
+   * that are still in use. This latter point is only an issue on Windows and
+   * a few other systems.
+   *
+   * If the moveToTrash parameter is true we first rename the given directory
+   * "foo.Trash123" (where "foo" is the original directory name, and "123" is
+   * a random number, in order to support multiple concurrent deletes). The
+   * directory is then deleted, file-by-file, on a background thread.
+   *
+   * If the moveToTrash parameter is false, then the given directory is deleted
+   * directly.
+   *
+   * If 'delay' is non-zero, the directory will not be deleted until the
+   * specified number of milliseconds have passed. (The directory is still
+   * renamed immediately if 'moveToTrash' is passed, so upon return it is safe
+   * to create a directory with the same name).
+   */
+  static nsresult DeleteDir(nsIFile *dir, bool moveToTrash, PRUint32 delay = 0);
 
-/**
- * This routine returns the trash directory corresponding to the given 
- * directory.
- */
-NS_HIDDEN_(nsresult) GetTrashDir(nsIFile *dir, nsCOMPtr<nsIFile> *result);
+  /**
+   * Returns the trash directory corresponding to the given directory.
+   */
+  static nsresult GetTrashDir(nsIFile *dir, nsCOMPtr<nsIFile> *result);
+
+  /**
+   * Remove all trashes left from previous run. This function does nothing when
+   * called second and more times.
+   */
+  static nsresult RemoveOldTrashes(nsIFile *cacheDir);
+
+  static void TimerCallback(nsITimer *aTimer, void *arg);
+
+private:
+  friend class nsBlockOnBackgroundThreadEvent;
+  friend class nsDestroyThreadEvent;
+
+  nsresult InitThread();
+  void     DestroyThread();
+  nsresult PostTimer(void *arg, PRUint32 delay);
+  nsresult RemoveDir(nsIFile *file, bool *stopDeleting);
+
+  static nsDeleteDir * gInstance;
+  mozilla::Mutex       mLock;
+  mozilla::CondVar     mCondVar;
+  nsCOMArray<nsITimer> mTimers;
+  nsCOMPtr<nsIThread>  mThread;
+  bool                 mShutdownPending;
+  bool                 mStopDeleting;
+};
 
 #endif  // nsDeleteDir_h__
--- a/netwerk/cache/nsDiskCacheDevice.cpp
+++ b/netwerk/cache/nsDiskCacheDevice.cpp
@@ -414,17 +414,19 @@ nsDiskCacheDevice::Init()
     }
        
     if (!mCacheDirectory)
         return NS_ERROR_FAILURE;
 
     rv = mBindery.Init();
     if (NS_FAILED(rv))
         return rv;
-    
+
+    nsDeleteDir::RemoveOldTrashes(mCacheDirectory);
+
     // Open Disk Cache
     rv = OpenDiskCache();
     if (NS_FAILED(rv)) {
         (void) mCacheMap.Close(false);
         return rv;
     }
 
     mInitialized = true;
@@ -439,27 +441,16 @@ nsresult
 nsDiskCacheDevice::Shutdown()
 {
     nsCacheService::AssertOwnsLock();
 
     nsresult rv = Shutdown_Private(true);
     if (NS_FAILED(rv))
         return rv;
 
-    if (mCacheDirectory) {
-        // delete any trash files left-over before shutting down.
-        nsCOMPtr<nsIFile> trashDir;
-        GetTrashDir(mCacheDirectory, &trashDir);
-        if (trashDir) {
-            bool exists;
-            if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists)
-                DeleteDir(trashDir, false, true);
-        }
-    }
-
     return NS_OK;
 }
 
 
 nsresult
 nsDiskCacheDevice::Shutdown_Private(bool    flush)
 {
     CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
@@ -999,28 +990,26 @@ nsDiskCacheDevice::OpenDiskCache()
 {
     Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
     // if we don't have a cache directory, create one and open it
     bool exists;
     nsresult rv = mCacheDirectory->Exists(&exists);
     if (NS_FAILED(rv))
         return rv;
 
-    bool trashing = false;
     if (exists) {
         // Try opening cache map file.
         rv = mCacheMap.Open(mCacheDirectory);        
         // move "corrupt" caches to trash
         if (rv == NS_ERROR_FILE_CORRUPTED) {
             // delay delete by 1 minute to avoid IO thrash at startup
-            rv = DeleteDir(mCacheDirectory, true, false, 60000);
+            rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
             if (NS_FAILED(rv))
                 return rv;
             exists = false;
-            trashing = true;
         }
         else if (NS_FAILED(rv))
             return rv;
     }
 
     // if we don't have a cache directory, create one and open it
     if (!exists) {
         rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
@@ -1030,46 +1019,33 @@ nsDiskCacheDevice::OpenDiskCache()
             return rv;
     
         // reopen the cache map     
         rv = mCacheMap.Open(mCacheDirectory);
         if (NS_FAILED(rv))
             return rv;
     }
 
-    if (!trashing) {
-        // delete any trash files leftover from a previous run
-        nsCOMPtr<nsIFile> trashDir;
-        GetTrashDir(mCacheDirectory, &trashDir);
-        if (trashDir) {
-            bool exists;
-            if (NS_SUCCEEDED(trashDir->Exists(&exists)) && exists) {
-                // be paranoid and delete immediately if leftover
-                DeleteDir(trashDir, false, false);
-            }
-        }
-    }
-
     return NS_OK;
 }
 
 
 nsresult
 nsDiskCacheDevice::ClearDiskCache()
 {
     if (mBindery.ActiveBindings())
         return NS_ERROR_CACHE_IN_USE;
 
     nsresult rv = Shutdown_Private(false);  // false: don't bother flushing
     if (NS_FAILED(rv))
         return rv;
 
     // If the disk cache directory is already gone, then it's not an error if
     // we fail to delete it ;-)
-    rv = DeleteDir(mCacheDirectory, true, false);
+    rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
     if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
         return rv;
 
     return Init();
 }
 
 
 nsresult
--- a/toolkit/components/telemetry/TelemetryHistograms.h
+++ b/toolkit/components/telemetry/TelemetryHistograms.h
@@ -230,16 +230,19 @@ HISTOGRAM(MOZ_SQLITE_URLCLASSIFIER_WRITE
 HISTOGRAM(MOZ_SQLITE_WEBAPPS_WRITE_B, 1, 32768, 3, LINEAR, "SQLite write (bytes)")
 HISTOGRAM(MOZ_SQLITE_OTHER_WRITE_B, 1, 32768, 3, LINEAR, "SQLite write (bytes)")
 HISTOGRAM(MOZ_STORAGE_ASYNC_REQUESTS_MS, 1, 32768, 20, EXPONENTIAL, "mozStorage async requests completion (ms)")
 HISTOGRAM_BOOLEAN(MOZ_STORAGE_ASYNC_REQUESTS_SUCCESS, "mozStorage async requests success")
 HISTOGRAM(STARTUP_MEASUREMENT_ERRORS, 1, mozilla::StartupTimeline::MAX_EVENT_ID, mozilla::StartupTimeline::MAX_EVENT_ID + 1, LINEAR, "Flags errors in startup calculation()")
 HISTOGRAM(NETWORK_DISK_CACHE_OPEN, 1, 10000, 10, EXPONENTIAL, "Time spent opening disk cache (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_TRASHRENAME, 1, 10000, 10, EXPONENTIAL, "Time spent renaming bad Cache to Cache.Trash (ms)")
 HISTOGRAM(NETWORK_DISK_CACHE_DELETEDIR, 1, 10000, 10, EXPONENTIAL, "Time spent deleting disk cache (ms)")
+HISTOGRAM(NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN, 1, 10000, 10, EXPONENTIAL, "Time spent during showdown stopping thread deleting old disk cache (ms)")
+HISTOGRAM(NETWORK_DISK_CACHE_SHUTDOWN, 1, 10000, 10, EXPONENTIAL, "Total Time spent (ms) during disk cache showdown")
+HISTOGRAM(NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE, 1, 10000, 10, EXPONENTIAL, "Time spent (ms) during showdown deleting disk cache for 'clear private data' option")
 
 /**
  * Url-Classifier telemetry
  */
 #ifdef MOZ_URL_CLASSIFIER
 HISTOGRAM(URLCLASSIFIER_PS_FILELOAD_TIME, 1, 1000, 10, EXPONENTIAL, "Time spent loading PrefixSet from file (ms)")
 HISTOGRAM(URLCLASSIFIER_PS_FALLOCATE_TIME, 1, 1000, 10, EXPONENTIAL, "Time spent fallocating PrefixSet (ms)")
 HISTOGRAM(URLCLASSIFIER_PS_CONSTRUCT_TIME, 1, 5000, 15, EXPONENTIAL, "Time spent constructing PrefixSet from DB (ms)")