Bug 1357676 - Implement batch eviction r=jdm
authorKershaw Chang <kershaw@mozilla.com>
Mon, 03 Sep 2018 14:49:58 +0000
changeset 434484 a596012f29db4681255057a35690f251a73b7282
parent 434483 fd75f8567ce97653176559bfedd37afd46b36d78
child 434485 5254a8f44df4ac705bf05fe55476f476a37bf8b9
push id34562
push userdvarga@mozilla.com
push dateMon, 03 Sep 2018 21:52:32 +0000
treeherdermozilla-central@42469f001fcb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs1357676
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1357676 - Implement batch eviction r=jdm 1. Add network.cookie.QuotaPerHost, which has the default value 150. 2. When the cookies exceed more than 180, evict cookies to 150. 3. The concept of eviction is to sort all cookies by whether the cookie is expired and the cookie's last access time. Then, evict cookies by the given count. 4. Details of evict algorithm: 4.1 Create a priority queue and push all cookies in it. 4.2 Use custom comparator to compare the cookie by expiry and last access. 4.3 Pop 30(180 - 150) cookies from the queue and append them to an output array. Differential Revision: https://phabricator.services.mozilla.com/D3342
modules/libpref/init/all.js
netwerk/cookie/nsCookieService.cpp
netwerk/cookie/nsCookieService.h
netwerk/cookie/test/unit/test_eviction.js
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2292,16 +2292,19 @@ pref("network.cookie.ipc.sync",         
 pref("network.cookie.lifetimePolicy",       0);
 
 // The interval in seconds to move the cookies in the child process.
 // Set to 0 to disable moving the cookies.
 pref("network.cookie.move.interval_sec",    10);
 
 pref("network.cookie.maxNumber", 3000);
 pref("network.cookie.maxPerHost", 180);
+// Cookies quota for each host. If cookies exceed the limit maxPerHost,
+// (maxPerHost - quotaPerHost) cookies will be evicted.
+pref("network.cookie.quotaPerHost", 150);
 
 // The PAC file to load.  Ignored unless network.proxy.type is 2.
 pref("network.proxy.autoconfig_url", "");
 // Strip off paths when sending URLs to PAC scripts
 pref("network.proxy.autoconfig_url.include_path", false);
 
 // If we cannot load the PAC file, then try again (doubling from interval_min
 // until we reach interval_max or the PAC file is successfully loaded).
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -56,16 +56,17 @@
 #include "nsNetCID.h"
 #include "mozilla/storage.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Telemetry.h"
 #include "nsIConsoleService.h"
+#include "nsTPriorityQueue.h"
 #include "nsVariant.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 // Create key from baseDomain that will access the default cookie namespace.
 // TODO: When we figure out what the API will look like for nsICookieManager{2}
 // on content processes (see bug 777620), change to use the appropriate app
@@ -114,23 +115,25 @@ static const int64_t kCookiePurgeAge =
 
 #undef  ADD_TEN_PERCENT
 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10)
 
 // default limits for the cookie list. these can be tuned by the
 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively.
 static const uint32_t kMaxNumberOfCookies = 3000;
 static const uint32_t kMaxCookiesPerHost  = 180;
+static const uint32_t kCookieQuotaPerHost = 150;
 static const uint32_t kMaxBytesPerCookie  = 4096;
 static const uint32_t kMaxBytesPerPath    = 1024;
 
 // pref string constants
 static const char kPrefCookieBehavior[]       = "network.cookie.cookieBehavior";
 static const char kPrefMaxNumberOfCookies[]   = "network.cookie.maxNumber";
 static const char kPrefMaxCookiesPerHost[]    = "network.cookie.maxPerHost";
+static const char kPrefCookieQuotaPerHost[]   = "network.cookie.quotaPerHost";
 static const char kPrefCookiePurgeAge[]       = "network.cookie.purgeAge";
 static const char kPrefThirdPartySession[]    = "network.cookie.thirdparty.sessionOnly";
 static const char kPrefThirdPartyNonsecureSession[] = "network.cookie.thirdparty.nonsecureSessionOnly";
 static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone";
 
 // For telemetry COOKIE_LEAVE_SECURE_ALONE
 #define BLOCKED_SECURE_SET_FROM_HTTP          0
 #define BLOCKED_DOWNGRADE_SECURE_INEXACT      1
@@ -511,16 +514,36 @@ public:
     MOZ_ASSERT(cookieManager);
 
     return cookieManager->RemoveCookiesWithOriginAttributes(nsDependentString(aData), EmptyCString());
   }
 };
 
 NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver)
 
+// comparator class for sorting cookies by entry and index.
+class CompareCookiesByIndex {
+public:
+  bool Equals(const nsListIter &a, const nsListIter &b) const
+  {
+    NS_ASSERTION(a.entry != b.entry || a.index != b.index,
+      "cookie indexes should never be equal");
+    return false;
+  }
+
+  bool LessThan(const nsListIter &a, const nsListIter &b) const
+  {
+    // compare by entryclass pointer, then by index.
+    if (a.entry != b.entry)
+      return a.entry < b.entry;
+
+    return a.index < b.index;
+  }
+};
+
 } // namespace
 
 size_t
 nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf);
 
   amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf);
@@ -607,16 +630,17 @@ NS_IMPL_ISUPPORTS(nsCookieService,
 nsCookieService::nsCookieService()
  : mDBState(nullptr)
  , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT)
  , mThirdPartySession(false)
  , mThirdPartyNonsecureSession(false)
  , mLeaveSecureAlone(true)
  , mMaxNumberOfCookies(kMaxNumberOfCookies)
  , mMaxCookiesPerHost(kMaxCookiesPerHost)
+ , mCookieQuotaPerHost(kCookieQuotaPerHost)
  , mCookiePurgeAge(kCookiePurgeAge)
  , mThread(nullptr)
  , mMonitor("CookieThread")
  , mInitializedDBStates(false)
  , mInitializedDBConn(false)
 {
 }
 
@@ -2382,16 +2406,35 @@ already_AddRefed<nsIArray>
 nsCookieService::CreatePurgeList(nsICookie2* aCookie)
 {
   nsCOMPtr<nsIMutableArray> removedList =
     do_CreateInstance(NS_ARRAY_CONTRACTID);
   removedList->AppendElement(aCookie);
   return removedList.forget();
 }
 
+void
+nsCookieService::CreateOrUpdatePurgeList(nsIArray** aPurgedList, nsICookie2* aCookie)
+{
+  if (!*aPurgedList) {
+    COOKIE_LOGSTRING(LogLevel::Debug, ("Creating new purge list"));
+    nsCOMPtr<nsIArray> purgedList = CreatePurgeList(aCookie);
+    purgedList.forget(aPurgedList);
+    return;
+  }
+
+  nsCOMPtr<nsIMutableArray> purgedList = do_QueryInterface(*aPurgedList);
+  if (purgedList) {
+    COOKIE_LOGSTRING(LogLevel::Debug, ("Updating existing purge list"));
+    purgedList->AppendElement(aCookie);
+  } else {
+    COOKIE_LOGSTRING(LogLevel::Debug, ("Could not QI aPurgedList!"));
+  }
+}
+
 /******************************************************************************
  * nsCookieService:
  * public transaction helper impl
  ******************************************************************************/
 
 NS_IMETHODIMP
 nsCookieService::RunInTransaction(nsICookieTransactionCallback* aCallback)
 {
@@ -2425,18 +2468,25 @@ nsCookieService::PrefChanged(nsIPrefBran
 {
   int32_t val;
   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
     mCookieBehavior = (uint8_t) LIMIT(val, 0, nsICookieService::BEHAVIOR_LAST, 0);
 
   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val)))
     mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies);
 
-  if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val)))
-    mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost);
+  if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieQuotaPerHost, &val))) {
+    mCookieQuotaPerHost =
+      (uint16_t) LIMIT(val, 1, mMaxCookiesPerHost - 1, kCookieQuotaPerHost);
+  }
+
+  if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val))) {
+    mMaxCookiesPerHost =
+      (uint16_t) LIMIT(val, mCookieQuotaPerHost + 1, 0xFFFF, kMaxCookiesPerHost);
+  }
 
   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) {
     mCookiePurgeAge =
       int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC;
   }
 
   bool boolval;
   if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval)))
@@ -3778,45 +3828,51 @@ nsCookieService::AddInternal(const nsCoo
       COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader,
         "cookie has already expired");
       return;
     }
 
     // check if we have to delete an old cookie.
     nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
-      nsListIter iter;
+      nsTArray<nsListIter> removedIterList;
       // Prioritize evicting insecure cookies.
       // (draft-ietf-httpbis-cookie-alone section 3.3)
       mozilla::Maybe<bool> optionalSecurity = mLeaveSecureAlone ? Some(false) : Nothing();
-      int64_t oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, optionalSecurity, iter);
-      if (iter.entry == nullptr) {
+      uint32_t limit = mMaxCookiesPerHost - mCookieQuotaPerHost;
+      FindStaleCookies(entry, currentTime, optionalSecurity, removedIterList, limit);
+      if (removedIterList.Length() == 0) {
         if (aCookie->IsSecure()) {
           // It's valid to evict a secure cookie for another secure cookie.
-          oldestCookieTime = FindStaleCookie(entry, currentTime, aHostURI, Some(true), iter);
+          FindStaleCookies(entry, currentTime, Some(true), removedIterList, limit);
         } else {
           Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE,
                                 EVICTING_SECURE_BLOCKED);
           COOKIE_LOGEVICTED(aCookie,
             "Too many cookies for this domain and the new cookie is not a secure cookie");
           return;
         }
       }
 
-      MOZ_ASSERT(iter.entry);
-
-      oldCookie = iter.Cookie();
-      if (oldestCookieTime > 0 && mLeaveSecureAlone) {
-        TelemetryForEvictingStaleCookie(oldCookie, oldestCookieTime);
+      MOZ_ASSERT(!removedIterList.IsEmpty());
+      // Sort |removedIterList| by index again, since we have to remove the cookie
+      // in the reverse order.
+      removedIterList.Sort(CompareCookiesByIndex());
+      for (auto it = removedIterList.rbegin(); it != removedIterList.rend(); it++) {
+        RefPtr<nsCookie> evictedCookie = (*it).Cookie();
+        if (mLeaveSecureAlone && evictedCookie->Expiry() <= currentTime) {
+          TelemetryForEvictingStaleCookie(evictedCookie,
+                                          evictedCookie->LastAccessed());
+        }
+        COOKIE_LOGEVICTED(evictedCookie, "Too many cookies for this domain");
+        RemoveCookieFromList(*it);
+        CreateOrUpdatePurgeList(getter_AddRefs(purgedList), evictedCookie);
+        MOZ_ASSERT((*it).entry);
       }
 
-      // remove the oldest cookie from the domain
-      RemoveCookieFromList(iter);
-      COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
-      purgedList = CreatePurgeList(oldCookie);
     } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
       int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime;
       int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge);
       if (maxAge >= purgeAge) {
         // we're over both size and age limits by 10%; time to purge the table!
         // do this by:
         // 1) removing expired cookies;
         // 2) evicting the balance of old cookies until we reach the size limit.
@@ -4568,36 +4624,16 @@ public:
     int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed();
     if (result != 0)
       return result < 0;
 
     return a.Cookie()->CreationTime() < b.Cookie()->CreationTime();
   }
 };
 
-// comparator class for sorting cookies by entry and index.
-class CompareCookiesByIndex {
-public:
-  bool Equals(const nsListIter &a, const nsListIter &b) const
-  {
-    NS_ASSERTION(a.entry != b.entry || a.index != b.index,
-      "cookie indexes should never be equal");
-    return false;
-  }
-
-  bool LessThan(const nsListIter &a, const nsListIter &b) const
-  {
-    // compare by entryclass pointer, then by index.
-    if (a.entry != b.entry)
-      return a.entry < b.entry;
-
-    return a.index < b.index;
-  }
-};
-
 // purges expired and old cookies in a batch operation.
 already_AddRefed<nsIArray>
 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec)
 {
   NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty");
 
   uint32_t initialCookieCount = mDBState->cookieCount;
   COOKIE_LOGSTRING(LogLevel::Debug,
@@ -4770,104 +4806,85 @@ nsCookieService::CookieExistsNative(nsIC
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsListIter iter;
   *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes),
                              host, name, path, iter);
   return NS_OK;
 }
 
-// For a given base domain, find either an expired cookie or the oldest cookie
-// by lastAccessed time.
-int64_t
-nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
-                                 int64_t aCurrentTime,
-                                 nsIURI* aSource,
-                                 const mozilla::Maybe<bool> &aIsSecure,
-                                 nsListIter &aIter)
+// Cookie comparator for the priority queue used in FindStaleCookies.
+// Note that the expired cookie has the highest priority.
+// Other non-expired cookies are sorted by their age.
+class CookieIterComparator {
+private:
+  CompareCookiesByAge mAgeComparator;
+  int64_t mCurrentTime;
+
+public:
+  explicit CookieIterComparator(int64_t aTime)
+    : mCurrentTime(aTime) {}
+
+  bool LessThan(const nsListIter& lhs, const nsListIter& rhs)
+  {
+    bool lExpired = lhs.Cookie()->Expiry() <= mCurrentTime;
+    bool rExpired = rhs.Cookie()->Expiry() <= mCurrentTime;
+    if (lExpired && !rExpired) {
+      return true;
+    }
+
+    if (!lExpired && rExpired) {
+      return false;
+    }
+
+    return mAgeComparator.LessThan(lhs, rhs);
+  }
+};
+
+// Given the output iter array and the count limit, find cookies
+// sort by expiry and lastAccessed time.
+void
+nsCookieService::FindStaleCookies(nsCookieEntry *aEntry,
+                                  int64_t aCurrentTime,
+                                  const mozilla::Maybe<bool> &aIsSecure,
+                                  nsTArray<nsListIter>& aOutput,
+                                  uint32_t aLimit)
 {
-  aIter.entry = nullptr;
-  bool requireHostMatch = true;
-  nsAutoCString baseDomain, sourceHost, sourcePath;
-  if (aSource) {
-    GetBaseDomain(mTLDService, aSource, baseDomain, requireHostMatch);
-    aSource->GetAsciiHost(sourceHost);
-    sourcePath = GetPathFromURI(aSource);
-  }
+  MOZ_ASSERT(aLimit);
 
   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
-
-  int64_t oldestNonMatchingCookieTime = 0;
-  nsListIter oldestNonMatchingCookie;
-  oldestNonMatchingCookie.entry = nullptr;
-
-  int64_t oldestCookieTime = 0;
-  nsListIter oldestCookie;
-  oldestCookie.entry = nullptr;
-
-  int64_t actualOldestCookieTime = cookies.Length() ? cookies[0]->LastAccessed() : 0;
+  aOutput.Clear();
+
+  CookieIterComparator comp(aCurrentTime);
+  nsTPriorityQueue<nsListIter, CookieIterComparator> queue(comp);
+
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     nsCookie *cookie = cookies[i];
 
-    // If we found an expired cookie, we're done.
     if (cookie->Expiry() <= aCurrentTime) {
-      aIter.entry = aEntry;
-      aIter.index = i;
-      return -1;
+      queue.Push(nsListIter(aEntry, i));
+      continue;
     }
 
-    int64_t lastAccessed = cookie->LastAccessed();
-    // Record the age of the oldest cookie that is stored for this host.
-    // oldestCookieTime is the age of the oldest cookie with a matching
-    // secure flag, which may be more recent than an older cookie with
-    // a non-matching secure flag.
-    if (actualOldestCookieTime > lastAccessed) {
-      actualOldestCookieTime = lastAccessed;
-    }
     if (aIsSecure.isSome() && !aIsSecure.value()) {
-      // We want to look for the oldest non-secure cookie first time through,
-      // then find the oldest secure cookie the second time we are called.
+      // We want to look for the non-secure cookie first time through,
+      // then find the secure cookie the second time this function is called.
       if (cookie->IsSecure()) {
         continue;
       }
     }
 
-    // This cookie is a candidate for eviction if we have no information about
-    // the source request, or if it is not a path or domain match against the
-    // source request.
-    bool isPrimaryEvictionCandidate = true;
-    if (aSource) {
-      isPrimaryEvictionCandidate = !PathMatches(cookie, sourcePath) || !DomainMatches(cookie, sourceHost);
-    }
-
-    if (isPrimaryEvictionCandidate &&
-        (!oldestNonMatchingCookie.entry ||
-         oldestNonMatchingCookieTime > lastAccessed)) {
-      oldestNonMatchingCookieTime = lastAccessed;
-      oldestNonMatchingCookie.entry = aEntry;
-      oldestNonMatchingCookie.index = i;
-    }
-
-    // Check if we've found the oldest cookie so far.
-    if (!oldestCookie.entry || oldestCookieTime > lastAccessed) {
-      oldestCookieTime = lastAccessed;
-      oldestCookie.entry = aEntry;
-      oldestCookie.index = i;
-    }
+    queue.Push(nsListIter(aEntry, i));
   }
 
-  // Prefer to evict the oldest cookie with a non-matching path/domain,
-  // followed by the oldest matching cookie.
-  if (oldestNonMatchingCookie.entry) {
-    aIter = oldestNonMatchingCookie;
-  } else {
-    aIter = oldestCookie;
+  uint32_t count = 0;
+  while (!queue.IsEmpty() && count < aLimit) {
+    aOutput.AppendElement(queue.Pop());
+    count++;
   }
-
-  return actualOldestCookieTime;
 }
 
 void
 nsCookieService::TelemetryForEvictingStaleCookie(nsCookie *aEvicted,
                                                  int64_t oldestCookieTime)
 {
   // We need to record the evicting cookie to telemetry.
   if (!aEvicted->IsSecure()) {
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -312,23 +312,24 @@ class nsCookieService final : public nsI
     static bool                   CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, bool aRequireHostMatch);
     static bool                   CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static bool                   CheckPrefixes(nsCookieAttributes &aCookie, bool aSecureRequest);
     static bool                   GetExpiry(nsCookieAttributes &aCookie, int64_t aServerTime, int64_t aCurrentTime);
     void                          RemoveAllFromMemory();
     already_AddRefed<nsIArray>    PurgeCookies(int64_t aCurrentTimeInUsec);
     bool                          FindCookie(const nsCookieKey& aKey, const nsCString& aHost, const nsCString& aName, const nsCString& aPath, nsListIter &aIter);
     bool                          FindSecureCookie(const nsCookieKey& aKey, nsCookie* aCookie);
-    int64_t                       FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsIURI* aSource, const mozilla::Maybe<bool> &aIsSecure, nsListIter &aIter);
+    void                          FindStaleCookies(nsCookieEntry *aEntry, int64_t aCurrentTime, const mozilla::Maybe<bool> &aIsSecure, nsTArray<nsListIter>& aOutput, uint32_t aLimit);
     void                          TelemetryForEvictingStaleCookie(nsCookie* aEvicted, int64_t oldestCookieTime);
     void                          NotifyRejected(nsIURI *aHostURI, nsIChannel* aChannel, uint32_t aRejectedReason);
     void                          NotifyThirdParty(nsIURI *aHostURI, bool aAccepted, nsIChannel *aChannel);
     void                          NotifyChanged(nsISupports *aSubject, const char16_t *aData, bool aOldCookieIsSession = false, bool aFromHttp = false);
     void                          NotifyPurged(nsICookie2* aCookie);
     already_AddRefed<nsIArray>    CreatePurgeList(nsICookie2* aCookie);
+    void                          CreateOrUpdatePurgeList(nsIArray** aPurgeList, nsICookie2* aCookie);
     void                          UpdateCookieOldestTime(DBState* aDBState, nsCookie* aCookie);
 
     nsresult                      GetCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern& aPattern, const nsCString& aBaseDomain, nsISimpleEnumerator **aEnumerator);
     nsresult                      RemoveCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern& aPattern, const nsCString& aBaseDomain);
 
     /**
      * This method is a helper that allows calling nsICookieManager::Remove()
      * with OriginAttributes parameter.
@@ -357,16 +358,17 @@ class nsCookieService final : public nsI
 
     // cached prefs
     uint8_t                       mCookieBehavior; // BEHAVIOR_{ACCEPT, REJECTFOREIGN, REJECT, LIMITFOREIGN}
     bool                          mThirdPartySession;
     bool                          mThirdPartyNonsecureSession;
     bool                          mLeaveSecureAlone;
     uint16_t                      mMaxNumberOfCookies;
     uint16_t                      mMaxCookiesPerHost;
+    uint16_t                      mCookieQuotaPerHost;
     int64_t                       mCookiePurgeAge;
 
     // thread
     nsCOMPtr<nsIThread>           mThread;
     mozilla::Monitor              mMonitor;
     mozilla::Atomic<bool>         mInitializedDBStates;
     mozilla::Atomic<bool>         mInitializedDBConn;
     mozilla::TimeStamp            mEndInitDBConn;
--- a/netwerk/cookie/test/unit/test_eviction.js
+++ b/netwerk/cookie/test/unit/test_eviction.js
@@ -4,225 +4,79 @@ const BASE_HOSTNAMES = ["example.org", "
 const SUBDOMAINS = ["", "pub.", "www.", "other."];
 
 const cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
 const cm = cs.QueryInterface(Ci.nsICookieManager);
 
 function run_test() {
     var tests = [];
     Services.prefs.setIntPref("network.cookie.staleThreshold", 0);
-    for (var host of BASE_HOSTNAMES) {
-        var base = SUBDOMAINS[0] + host;
-        var sub = SUBDOMAINS[1] + host;
-        var other = SUBDOMAINS[2] + host;
-        var another = SUBDOMAINS[3] + host;
-        tests.push([host, test_basic_eviction.bind(this, base, sub, other, another)]);
-        add_task(async function a() {
-            var t = tests.splice(0, 1)[0];
-            info('testing with host ' + t[0]);
-            await t[1]();
-            cm.removeAll();
-        });
-        tests.push([host, test_domain_or_path_matches_not_both.bind(this, base, sub, other, another)]);
-        add_task(async function() {
-            var t = tests.splice(0, 1)[0];
-            info('testing with host ' + t[0]);
-            await t[1]();
-            cm.removeAll();
-        });
-    }
     add_task(async function() {
-        await test_localdomain();
+        await test_basic_eviction("example.org");
         cm.removeAll();
     });
 
-    add_task(async function() {
-        await test_path_prefix();
-    });
-
     run_next_test();
 }
 
-// Verify that cookies that share a path prefix with the URI path are still considered
-// candidates for eviction, since the paths do not actually match.
-async function test_path_prefix() {
-    Services.prefs.setIntPref("network.cookie.maxPerHost", 2);
-
-    const BASE_URI = Services.io.newURI("http://example.org/");
-    const BASE_BAR = Services.io.newURI("http://example.org/bar/");
-    const BASE_BARBAR = Services.io.newURI("http://example.org/barbar/");
-
-    await setCookie("session_first", null, null, null, BASE_URI);
-    await setCookie("session_second", null, "/bar", null, BASE_BAR);
-    verifyCookies(['session_first', 'session_second'], BASE_URI);
-
-    await setCookie("session_third", null, "/barbar", null, BASE_BARBAR);
-    verifyCookies(['session_first', 'session_third'], BASE_URI);
-}
-
-// Verify that subdomains of localhost are treated as separate hosts and aren't considered
-// candidates for eviction.
-async function test_localdomain() {
-    Services.prefs.setIntPref("network.cookie.maxPerHost", 2);
-
-    const BASE_URI = Services.io.newURI("http://localhost");
-    const BASE_BAR = Services.io.newURI("http://localhost/bar");
-    const OTHER_URI = Services.io.newURI("http://other.localhost");
-    const OTHER_BAR = Services.io.newURI("http://other.localhost/bar");
-    
-    await setCookie("session_no_path", null, null, null, BASE_URI);
-    await setCookie("session_bar_path", null, "/bar", null, BASE_BAR);
-
-    await setCookie("session_no_path", null, null, null, OTHER_URI);
-    await setCookie("session_bar_path", null, "/bar", null, OTHER_BAR);
-
-    verifyCookies(['session_no_path',
-                   'session_bar_path'], BASE_URI);
-    verifyCookies(['session_no_path',
-                   'session_bar_path'], OTHER_URI);
-
-    await setCookie("session_another_no_path", null, null, null, BASE_URI);
-    verifyCookies(['session_no_path',
-                   'session_another_no_path'], BASE_URI);
-
-    await setCookie("session_another_no_path", null, null, null, OTHER_URI);
-    verifyCookies(['session_no_path',
-                   'session_another_no_path'], OTHER_URI);
-}
-
-// Ensure that cookies are still considered candidates for eviction if either the domain
-// or path matches, but not both.
-async function test_domain_or_path_matches_not_both(base_host,
-                                               subdomain_host,
-                                               other_subdomain_host,
-                                               another_subdomain_host) {
-    Services.prefs.setIntPref("network.cookie.maxPerHost", 2);
-
-    const BASE_URI = Services.io.newURI("http://" + base_host);
-    const PUB_FOO_PATH = Services.io.newURI("http://" + subdomain_host + "/foo/");
-    const WWW_BAR_PATH = Services.io.newURI("http://" + other_subdomain_host + "/bar/");
-    const OTHER_BAR_PATH = Services.io.newURI("http://" + another_subdomain_host + "/bar/");
-    const PUB_BAR_PATH = Services.io.newURI("http://" + subdomain_host + "/bar/");
-    const WWW_FOO_PATH = Services.io.newURI("http://" + other_subdomain_host + "/foo/");
-
-    await setCookie("session_pub_with_foo_path", subdomain_host, "/foo", null, PUB_FOO_PATH);
-    await setCookie("session_www_with_bar_path", other_subdomain_host, "/bar", null, WWW_BAR_PATH);
-    verifyCookies(['session_pub_with_foo_path',
-                   'session_www_with_bar_path'], BASE_URI);
-
-    await setCookie("session_pub_with_bar_path", subdomain_host, "/bar", null, PUB_BAR_PATH);
-    verifyCookies(['session_www_with_bar_path',
-                   'session_pub_with_bar_path'], BASE_URI);
-
-    await setCookie("session_other_with_bar_path", another_subdomain_host, "/bar", null, OTHER_BAR_PATH);
-    verifyCookies(['session_pub_with_bar_path',
-                   'session_other_with_bar_path'], BASE_URI);
-}
-
-async function test_basic_eviction(base_host, subdomain_host, other_subdomain_host) {
+async function test_basic_eviction(base_host) {
+    Services.prefs.setIntPref("network.cookie.quotaPerHost", 2);
     Services.prefs.setIntPref("network.cookie.maxPerHost", 5);
 
     const BASE_URI = Services.io.newURI("http://" + base_host);
-    const SUBDOMAIN_URI = Services.io.newURI("http://" + subdomain_host);
-    const OTHER_SUBDOMAIN_URI = Services.io.newURI("http://" + other_subdomain_host);
     const FOO_PATH = Services.io.newURI("http://" + base_host + "/foo/");
     const BAR_PATH = Services.io.newURI("http://" + base_host + "/bar/");
-    const ALL_SUBDOMAINS = '.' + base_host;
-    const OTHER_SUBDOMAIN = other_subdomain_host;
 
-    // Initialize the set of cookies with a mix of non-session cookies with no path,
-    // and session cookies with explicit paths. Any subsequent cookies added will cause
-    // existing cookies to be evicted.
-    await setCookie("non_session_non_path_non_domain", null, null, 100000, BASE_URI);
-    await setCookie("non_session_non_path_subdomain", ALL_SUBDOMAINS, null, 100000, SUBDOMAIN_URI);
-    await setCookie("session_non_path_pub_domain", OTHER_SUBDOMAIN, null, null, OTHER_SUBDOMAIN_URI);
-    await setCookie("session_foo_path", null, "/foo", null, FOO_PATH);
-    await setCookie("session_bar_path", null, "/bar", null, BAR_PATH);
-    verifyCookies(['non_session_non_path_non_domain',
-                   'non_session_non_path_subdomain',
-                   'session_non_path_pub_domain',
-                   'session_foo_path',
-                   'session_bar_path'], BASE_URI);
+    await setCookie("session_foo_path_1", null, "/foo", null, FOO_PATH);
+    await setCookie("session_foo_path_2", null, "/foo", null, FOO_PATH);
+    await setCookie("session_foo_path_3", null, "/foo", null, FOO_PATH);
+    await setCookie("session_foo_path_4", null, "/foo", null, FOO_PATH);
+    await setCookie("session_foo_path_5", null, "/foo", null, FOO_PATH);
+    verifyCookies(['session_foo_path_1',
+                   'session_foo_path_2',
+                   'session_foo_path_3',
+                   'session_foo_path_4',
+                   'session_foo_path_5'], BASE_URI);
 
-    // Ensure that cookies set for the / path appear more recent.
-    cs.getCookieString(OTHER_SUBDOMAIN_URI, null)
-    verifyCookies(['non_session_non_path_non_domain',
-                   'session_foo_path',
-                   'session_bar_path',
-                   'non_session_non_path_subdomain',
-                   'session_non_path_pub_domain'], BASE_URI);
+    // Check if cookies are evicted by creation time.
+    await setCookie("session_foo_path_6", null, "/foo", null, FOO_PATH);
+    verifyCookies(['session_foo_path_4',
+                   'session_foo_path_5',
+                   'session_foo_path_6'], BASE_URI);
 
-    // Evict oldest cookie that does not match example.org/foo (session_bar_path)
-    await setCookie("session_foo_path_2", null, "/foo", null, FOO_PATH);
-    verifyCookies(['non_session_non_path_non_domain',
-                   'session_foo_path',
-                   'non_session_non_path_subdomain',
-                   'session_non_path_pub_domain',
-                   'session_foo_path_2'], BASE_URI);
+    await setCookie("session_bar_path_1", null, "/bar", null, BAR_PATH);
+    await setCookie("session_bar_path_2", null, "/bar", null, BAR_PATH);
 
-    // Evict oldest cookie that does not match example.org/bar (session_foo_path)
-    await setCookie("session_bar_path_2", null, "/bar", null, BAR_PATH);
-    verifyCookies(['non_session_non_path_non_domain',
-                   'non_session_non_path_subdomain',
-                   'session_non_path_pub_domain',
-                   'session_foo_path_2',
+    verifyCookies(['session_foo_path_4',
+                   'session_foo_path_5',
+                   'session_foo_path_6',
+                   'session_bar_path_1',
                    'session_bar_path_2'], BASE_URI);
 
-    // Evict oldest cookie that does not match example.org/ (session_non_path_pub_domain)
-    await setCookie("non_session_non_path_non_domain_2", null, null, 100000, BASE_URI);
-    verifyCookies(['non_session_non_path_non_domain',
-                   'non_session_non_path_subdomain',
-                   'session_foo_path_2',
-                   'session_bar_path_2',
-                   'non_session_non_path_non_domain_2'], BASE_URI);
-
-    // Evict oldest cookie that does not match example.org/ (session_foo_path_2)
-    await setCookie("session_non_path_non_domain_3", null, null, null, BASE_URI);
-    verifyCookies(['non_session_non_path_non_domain',
-                   'non_session_non_path_subdomain',
-                   'session_bar_path_2',
-                   'non_session_non_path_non_domain_2',
-                   'session_non_path_non_domain_3'], BASE_URI);
-
-    // Evict oldest cookie; all such cookies match example.org/bar (non_session_non_path_non_domain)
-    await setCookie("non_session_bar_path_non_domain", null, null, 100000, BAR_PATH);
-    verifyCookies(['non_session_non_path_subdomain',
-                   'session_bar_path_2',
-                   'non_session_non_path_non_domain_2',
-                   'session_non_path_non_domain_3',
-                   'non_session_bar_path_non_domain'], BASE_URI);
+    // Check if cookies are evicted by last accessed time.
+    cs.getCookieString(FOO_PATH, null);
+    await setCookie("session_foo_path_7", null, "/foo", null, FOO_PATH);
+    verifyCookies(['session_foo_path_5',
+                   'session_foo_path_6',
+                   'session_foo_path_7'], BASE_URI);
 
-    // Evict oldest cookie that deose not match example.org/ (session_bar_path_2)
-    await setCookie("non_session_non_path_pub_domain", null, null, 100000, OTHER_SUBDOMAIN_URI);
-    verifyCookies(['non_session_non_path_subdomain',
-                   'non_session_non_path_non_domain_2',
-                   'session_non_path_non_domain_3',
-                   'non_session_bar_path_non_domain',
-                   'non_session_non_path_pub_domain'], BASE_URI);
+    await setCookie("non_session_expired_foo_path_1", null, "/foo", 1, FOO_PATH);
+    await setCookie("non_session_expired_foo_path_2", null, "/foo", 1, FOO_PATH);
+    verifyCookies(['session_foo_path_5',
+                   'session_foo_path_6',
+                   'session_foo_path_7',
+                   'non_session_expired_foo_path_1',
+                   'non_session_expired_foo_path_2'], BASE_URI);
 
-    // Evict oldest cookie that does not match example.org/bar (non_session_non_path_pub_domain)
-    await setCookie("non_session_bar_path_non_domain_2", null, '/bar', 100000, BAR_PATH);
-    verifyCookies(['non_session_non_path_subdomain',
-                   'non_session_non_path_non_domain_2',
-                   'session_non_path_non_domain_3',
-                   'non_session_bar_path_non_domain',
-                   'non_session_bar_path_non_domain_2'], BASE_URI);
-
-    // Evict oldest cookie that does not match example.org/ (non_session_bar_path_non_domain)
-    await setCookie("non_session_non_path_non_domain_4", null, null, 100000, BASE_URI);
-    verifyCookies(['non_session_non_path_subdomain',
-                   'non_session_non_path_non_domain_2',
-                   'session_non_path_non_domain_3',
-                   'non_session_bar_path_non_domain_2',
-                   'non_session_non_path_non_domain_4'], BASE_URI);
-
-    // At this point all remaining cookies have a path of / and either don't have a domain
-    // or have one that matches subdomains.
-    // They will therefore be evicted from oldest to newest if all new cookies added share
-    // similar characteristics.
+    // Check if expired cookies are evicted first.
+    await new Promise(resolve => do_timeout(1000, resolve));
+    await setCookie("session_foo_path_8", null, "/foo", null, FOO_PATH);
+    verifyCookies(['session_foo_path_6',
+                   'session_foo_path_7',
+                   'session_foo_path_8'], BASE_URI);
 }
 
 // Verify that the given cookie names exist, and are ordered from least to most recently accessed
 function verifyCookies(names, uri) {
     Assert.equal(cm.countCookiesFromHost(uri.host), names.length);
     let actual_cookies = [];
     for (let cookie of cm.getCookiesFromHost(uri.host, {})) {
         actual_cookies.push(cookie);