Bug 1444588 - Move LRUCache Initialization to startup rather than lazily. r=baku a=jcristau
authorTom Ritter <tom@mozilla.com>
Tue, 13 Mar 2018 10:10:22 -0700
changeset 462732 a26ab71d2540d93913c0b41cbaafcc1fbd267eac
parent 462731 b4987640fadead26f71bc7666c106feaacf855f7
child 462733 2ac6479eca3f8b75699b131ee6b8754ec9350882
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, jcristau
bugs1444588
milestone60.0
Bug 1444588 - Move LRUCache Initialization to startup rather than lazily. r=baku a=jcristau Before, we would initialize LRUCache on the first instance of calling the Timer Precision Reduction functions. We would both allocate and initialize it, and call ClearOnShutdown. ClearOnShutdown can only be called on the Main Thread, but it just so happened that we always did that, so there was no problem. Now that we are not calling precision reduction for system callers, we were initializing on a non-main-thread and we need to avoid that. In the future, we could reduce memory use IF we are not using the timer precision reduction functions by figuring out how to initialize this lazily but still on the main thread. For now, because we are using the timer precision reduction functions, doing so would not save us any memory. MozReview-Commit-ID: 6YGeAlCPReZ
toolkit/components/resistfingerprinting/nsRFPService.cpp
old mode 100644
new mode 100755
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -139,24 +139,26 @@ nsRFPService::IsTimerPrecisionReductionE
  * The below is a simple time-based Least Recently Used cache used to store the
  * result of a cryptographic hash function. It has LRU_CACHE_SIZE slots, and will
  * be used from multiple threads. It is thread-safe.
  */
 #define LRU_CACHE_SIZE         (45)
 #define HASH_DIGEST_SIZE_BITS  (256)
 #define HASH_DIGEST_SIZE_BYTES (HASH_DIGEST_SIZE_BITS / 8)
 
-class LRUCache
+class LRUCache final
 {
 public:
   LRUCache()
     : mLock("mozilla.resistFingerprinting.LRUCache") {
     this->cache.SetLength(LRU_CACHE_SIZE);
   }
 
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LRUCache)
+
   nsCString Get(long long aKey) {
     for (auto & cacheEntry : this->cache) {
       // Read optimistically befor locking
       if (cacheEntry.key == aKey) {
         MutexAutoLock lock(mLock);
 
         // Double check after we have a lock
         if (MOZ_UNLIKELY(cacheEntry.key != aKey)) {
@@ -197,16 +199,18 @@ public:
     lowestKey->key = aKey;
     lowestKey->data = aValue;
     lowestKey->accessTime = PR_Now();
     MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose, ("LRU Cache STORE with %lli", aKey));
   }
 
 
 private:
+  ~LRUCache() = default;
+
   struct CacheEntry {
     Atomic<long long, Relaxed> key;
     PRTime accessTime = 0;
     nsCString data;
 
     CacheEntry() {
       this->key = 0xFFFFFFFFFFFFFFFF;
       this->accessTime = 0;
@@ -219,17 +223,17 @@ private:
     }
   };
 
   AutoTArray<CacheEntry, LRU_CACHE_SIZE> cache;
   mozilla::Mutex mLock;
 };
 
 // We make a single LRUCache
-static StaticAutoPtr<LRUCache> sCache;
+static StaticRefPtr<LRUCache> sCache;
 
 /**
  * The purpose of this function is to deterministicly generate a random midpoint
  * between a lower clamped value and an upper clamped value. Assuming a clamping
  * resolution of 100, here is an example:
  *
  * |---------------------------------------|--------------------------|
  * lower clamped value (e.g. 300)          |           upper clamped value (400)
@@ -288,26 +292,28 @@ nsRFPService::RandomMidpoint(long long a
                              long long* aMidpointOut,
                              uint8_t * aSecretSeed /* = nullptr */)
 {
   nsresult rv;
   const int kSeedSize = 16;
   const int kClampTimesPerDigest = HASH_DIGEST_SIZE_BITS / 32;
   static uint8_t * sSecretMidpointSeed = nullptr;
 
-  if(MOZ_UNLIKELY(!sCache)) {
-    StaticMutexAutoLock lock(sLock);
-    if(MOZ_LIKELY(!sCache)) {
-      sCache = new LRUCache();
-      ClearOnShutdown(&sCache);
-    }
+  if(MOZ_UNLIKELY(!aMidpointOut)) {
+    return NS_ERROR_INVALID_ARG;
   }
 
-  if(MOZ_UNLIKELY(!aMidpointOut)) {
-    return NS_ERROR_INVALID_ARG;
+  RefPtr<LRUCache> cache;
+  {
+    StaticMutexAutoLock lock(sLock);
+    cache = sCache;
+  }
+
+  if(!cache) {
+    return NS_ERROR_FAILURE;
   }
 
   /*
    * Below, we will call a cryptographic hash function. That's expensive. We look for ways to
    * make it more efficient.
    *
    * We only need as much output from the hash function as the maximum resolution we will
    * ever support, because we will reduce the output modulo that value. The maximum resolution
@@ -323,17 +329,17 @@ nsRFPService::RandomMidpoint(long long a
    * kClampTimesPerDigest (just like we reduced the real time value to aClampedTime!)
    *
    * Then we hash _that_ value (assuming it's not in the cache) and index into the digest result
    * the appropriate bit offset.
    */
   long long reducedResolution = aResolutionUSec * kClampTimesPerDigest;
   long long extraClampedTime = (aClampedTimeUSec / reducedResolution) * reducedResolution;
 
-  nsCString hashResult = sCache->Get(extraClampedTime);
+  nsCString hashResult = cache->Get(extraClampedTime);
 
   if(hashResult.Length() != HASH_DIGEST_SIZE_BYTES) { // Cache Miss =(
     // If someone has pased in the testing-only parameter, replace our seed with it
     if (aSecretSeed != nullptr) {
       StaticMutexAutoLock lock(sLock);
       if (sSecretMidpointSeed) {
         delete[] sSecretMidpointSeed;
       }
@@ -390,17 +396,17 @@ nsRFPService::RandomMidpoint(long long a
      rv = hasher->Update((const uint8_t *)&extraClampedTime, sizeof(extraClampedTime));
      NS_ENSURE_SUCCESS(rv, rv);
 
      nsAutoCStringN<HASH_DIGEST_SIZE_BYTES> derivedSecret;
      rv = hasher->Finish(false, derivedSecret);
      NS_ENSURE_SUCCESS(rv, rv);
 
      // Finally, store it in the cache
-     sCache->Store(extraClampedTime, derivedSecret);
+     cache->Store(extraClampedTime, derivedSecret);
      hashResult = derivedSecret;
   }
 
   // Offset the appropriate index into the hash output, and then turn it into a random midpoint
   // between 0 and aResolutionUSec. Sometimes out input time is negative, we ride the negative
   // out to the end until we start doing pointer math. (We also triple check we're in bounds.)
   int byteOffset = abs(((aClampedTimeUSec - extraClampedTime) / aResolutionUSec) * 4);
   if (MOZ_UNLIKELY(byteOffset > (HASH_DIGEST_SIZE_BYTES - 4))) {
@@ -690,16 +696,22 @@ nsRFPService::Init()
   const char* tzValue = PR_GetEnv("TZ");
   if (tzValue) {
     mInitialTZValue = nsCString(tzValue);
   }
 
   // Call Update here to cache the values of the prefs and set the timezone.
   UpdateRFPPref();
 
+  // Create the LRU Cache when we initialize, to avoid accidently trying to
+  // create it (and call ClearOnShutdown) on a non-main-thread
+  if(!sCache) {
+    sCache = new LRUCache();
+  }
+
   return rv;
 }
 
 // This function updates only timing-related fingerprinting items
 void
 nsRFPService::UpdateTimers() {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -759,16 +771,21 @@ nsRFPService::UpdateRFPPref()
 
 void
 nsRFPService::StartShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 
+  StaticMutexAutoLock lock(sLock);
+  {
+    sCache = nullptr;
+  }
+
   if (obs) {
     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 
     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
 
     if (prefs) {
       prefs->RemoveObserver(RESIST_FINGERPRINTING_PREF, this);
       prefs->RemoveObserver(RFP_TIMER_PREF, this);