Bug 1443943 Move LRUCache Initialization to startup rather than lazily r?baku draft
authorTom Ritter <tom@mozilla.com>
Fri, 09 Mar 2018 11:52:33 -0600
changeset 766862 058e545e0e332cbace49747443fb0ea87eb30e57
parent 765547 415e9b18ca2a1532086d5e2d5d21343cd004b5fd
child 766863 73366a81861860182fc9b7f9562808f7ccbaf4bb
push id102423
push userbmo:tom@mozilla.com
push dateTue, 13 Mar 2018 14:13:38 +0000
reviewersbaku
bugs1443943
milestone60.0a1
Bug 1443943 Move LRUCache Initialization to startup rather than lazily r?baku 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: GqkfouVSeG8
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);