xpcom/ds/nsExpirationTracker.h

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #ifndef NSEXPIRATIONTRACKER_H_
     7 #define NSEXPIRATIONTRACKER_H_
     9 #include "mozilla/Attributes.h"
    11 #include "prlog.h"
    12 #include "nsTArray.h"
    13 #include "nsITimer.h"
    14 #include "nsCOMPtr.h"
    15 #include "nsAutoPtr.h"
    16 #include "nsComponentManagerUtils.h"
    17 #include "nsIObserver.h"
    18 #include "nsIObserverService.h"
    19 #include "mozilla/Services.h"
    20 #include "mozilla/Attributes.h"
    22 /**
    23  * Data used to track the expiration state of an object. We promise that this
    24  * is 32 bits so that objects that includes this as a field can pad and align
    25  * efficiently.
    26  */
    27 struct nsExpirationState {
    28   enum { NOT_TRACKED = (1U << 4) - 1,
    29          MAX_INDEX_IN_GENERATION = (1U << 28) - 1 };
    31   nsExpirationState() : mGeneration(NOT_TRACKED) {}
    32   bool IsTracked() { return mGeneration != NOT_TRACKED; }
    34   /**
    35    * The generation that this object belongs to, or NOT_TRACKED.
    36    */
    37   uint32_t mGeneration:4;
    38   uint32_t mIndexInGeneration:28;
    39 };
    41 /**
    42  * nsExpirationTracker can track the lifetimes and usage of a large number of
    43  * objects, and send a notification some window of time after a live object was
    44  * last used. This is very useful when you manage a large number of objects
    45  * and want to flush some after they haven't been used for a while.
    46  * nsExpirationTracker is designed to be very space and time efficient.
    47  * 
    48  * The type parameter T is the object type that we will track pointers to. T
    49  * must include an accessible method GetExpirationState() that returns a
    50  * pointer to an nsExpirationState associated with the object (preferably,
    51  * stored in a field of the object).
    52  * 
    53  * The parameter K is the number of generations that will be used. Increasing
    54  * the number of generations narrows the window within which we promise
    55  * to fire notifications, at a slight increase in space cost for the tracker.
    56  * We require 2 <= K <= nsExpirationState::NOT_TRACKED (currently 15).
    57  * 
    58  * To use this class, you need to inherit from it and override the
    59  * NotifyExpired() method.
    60  * 
    61  * The approach is to track objects in K generations. When an object is accessed
    62  * it moves from its current generation to the newest generation. Generations
    63  * are stored in a cyclic array; when a timer interrupt fires, we advance
    64  * the current generation pointer to effectively age all objects very efficiently.
    65  * By storing information in each object about its generation and index within its
    66  * generation array, we make removal of objects from a generation very cheap.
    67  * 
    68  * Future work:
    69  * -- Add a method to change the timer period?
    70  */
    71 template <class T, uint32_t K> class nsExpirationTracker {
    72   public:
    73     /**
    74      * Initialize the tracker.
    75      * @param aTimerPeriod the timer period in milliseconds. The guarantees
    76      * provided by the tracker are defined in terms of this period. If the
    77      * period is zero, then we don't use a timer and rely on someone calling
    78      * AgeOneGeneration explicitly.
    79      */
    80     nsExpirationTracker(uint32_t aTimerPeriod)
    81       : mTimerPeriod(aTimerPeriod), mNewestGeneration(0),
    82         mInAgeOneGeneration(false) {
    83       static_assert(K >= 2 && K <= nsExpirationState::NOT_TRACKED,
    84                     "Unsupported number of generations (must be 2 <= K <= 15)");
    85       mObserver = new ExpirationTrackerObserver();
    86       mObserver->Init(this);
    87     }
    88     ~nsExpirationTracker() {
    89       if (mTimer) {
    90         mTimer->Cancel();
    91       }
    92       mObserver->Destroy();
    93     }
    95     /**
    96      * Add an object to be tracked. It must not already be tracked. It will
    97      * be added to the newest generation, i.e., as if it was just used.
    98      * @return an error on out-of-memory
    99      */
   100     nsresult AddObject(T* aObj) {
   101       nsExpirationState* state = aObj->GetExpirationState();
   102       NS_ASSERTION(!state->IsTracked(), "Tried to add an object that's already tracked");
   103       nsTArray<T*>& generation = mGenerations[mNewestGeneration];
   104       uint32_t index = generation.Length();
   105       if (index > nsExpirationState::MAX_INDEX_IN_GENERATION) {
   106         NS_WARNING("More than 256M elements tracked, this is probably a problem");
   107         return NS_ERROR_OUT_OF_MEMORY;
   108       }
   109       if (index == 0) {
   110         // We might need to start the timer
   111         nsresult rv = CheckStartTimer();
   112         if (NS_FAILED(rv))
   113           return rv;
   114       }
   115       if (!generation.AppendElement(aObj))
   116         return NS_ERROR_OUT_OF_MEMORY;
   117       state->mGeneration = mNewestGeneration;
   118       state->mIndexInGeneration = index;
   119       return NS_OK;
   120     }
   122     /**
   123      * Remove an object from the tracker. It must currently be tracked.
   124      */
   125     void RemoveObject(T* aObj) {
   126       nsExpirationState* state = aObj->GetExpirationState();
   127       NS_ASSERTION(state->IsTracked(), "Tried to remove an object that's not tracked");
   128       nsTArray<T*>& generation = mGenerations[state->mGeneration];
   129       uint32_t index = state->mIndexInGeneration;
   130       NS_ASSERTION(generation.Length() > index &&
   131                    generation[index] == aObj, "Object is lying about its index");
   132       // Move the last object to fill the hole created by removing aObj
   133       uint32_t last = generation.Length() - 1;
   134       T* lastObj = generation[last];
   135       generation[index] = lastObj;
   136       lastObj->GetExpirationState()->mIndexInGeneration = index;
   137       generation.RemoveElementAt(last);
   138       state->mGeneration = nsExpirationState::NOT_TRACKED;
   139       // We do not check whether we need to stop the timer here. The timer
   140       // will check that itself next time it fires. Checking here would not
   141       // be efficient since we'd need to track all generations. Also we could
   142       // thrash by incessantly creating and destroying timers if someone
   143       // kept adding and removing an object from the tracker.
   144     }
   146     /**
   147      * Notify that an object has been used.
   148      * @return an error if we lost the object from the tracker...
   149      */
   150     nsresult MarkUsed(T* aObj) {
   151       nsExpirationState* state = aObj->GetExpirationState();
   152       if (mNewestGeneration == state->mGeneration)
   153         return NS_OK;
   154       RemoveObject(aObj);
   155       return AddObject(aObj);
   156     }
   158     /**
   159      * The timer calls this, but it can also be manually called if you want
   160      * to age objects "artifically". This can result in calls to NotifyExpired.
   161      */
   162     void AgeOneGeneration() {
   163       if (mInAgeOneGeneration) {
   164         NS_WARNING("Can't reenter AgeOneGeneration from NotifyExpired");
   165         return;
   166       }
   168       mInAgeOneGeneration = true;
   169       uint32_t reapGeneration = 
   170         mNewestGeneration > 0 ? mNewestGeneration - 1 : K - 1;
   171       nsTArray<T*>& generation = mGenerations[reapGeneration];
   172       // The following is rather tricky. We have to cope with objects being
   173       // removed from this generation either because of a call to RemoveObject
   174       // (or indirectly via MarkUsed) inside NotifyExpired. Fortunately no
   175       // objects can be added to this generation because it's not the newest
   176       // generation. We depend on the fact that RemoveObject can only cause
   177       // the indexes of objects in this generation to *decrease*, not increase.
   178       // So if we start from the end and work our way backwards we are guaranteed
   179       // to see each object at least once.
   180       uint32_t index = generation.Length();
   181       for (;;) {
   182         // Objects could have been removed so index could be outside
   183         // the array
   184         index = XPCOM_MIN(index, generation.Length());
   185         if (index == 0)
   186           break;
   187         --index;
   188         NotifyExpired(generation[index]);
   189       }
   190       // Any leftover objects from reapGeneration just end up in the new
   191       // newest-generation. This is bad form, though, so warn if there are any.
   192       if (!generation.IsEmpty()) {
   193         NS_WARNING("Expired objects were not removed or marked used");
   194       }
   195       // Free excess memory used by the generation array, since we probably
   196       // just removed most or all of its elements.
   197       generation.Compact();
   198       mNewestGeneration = reapGeneration;
   199       mInAgeOneGeneration = false;
   200     }
   202     /**
   203      * This just calls AgeOneGeneration K times. Under normal circumstances this
   204      * will result in all objects getting NotifyExpired called on them, but
   205      * if NotifyExpired itself marks some objects as used, then those objects
   206      * might not expire. This would be a good thing to call if we get into
   207      * a critically-low memory situation.
   208      */
   209     void AgeAllGenerations() {
   210       uint32_t i;
   211       for (i = 0; i < K; ++i) {
   212         AgeOneGeneration();
   213       }
   214     }
   216     class Iterator {
   217     private:
   218       nsExpirationTracker<T,K>* mTracker;
   219       uint32_t                  mGeneration;
   220       uint32_t                  mIndex;
   221     public:
   222       Iterator(nsExpirationTracker<T,K>* aTracker)
   223         : mTracker(aTracker), mGeneration(0), mIndex(0) {}
   224       T* Next() {
   225         while (mGeneration < K) {
   226           nsTArray<T*>* generation = &mTracker->mGenerations[mGeneration];
   227           if (mIndex < generation->Length()) {
   228             ++mIndex;
   229             return (*generation)[mIndex - 1];
   230           }
   231           ++mGeneration;
   232           mIndex = 0;
   233         }
   234         return nullptr;
   235       }
   236     };
   238     friend class Iterator;
   240     bool IsEmpty() {
   241       for (uint32_t i = 0; i < K; ++i) {
   242         if (!mGenerations[i].IsEmpty())
   243           return false;
   244       }
   245       return true;
   246     }
   248   protected:
   249     /**
   250      * This must be overridden to catch notifications. It is called whenever
   251      * we detect that an object has not been used for at least (K-1)*mTimerPeriod
   252      * milliseconds. If timer events are not delayed, it will be called within
   253      * roughly K*mTimerPeriod milliseconds after the last use. (Unless AgeOneGeneration
   254      * or AgeAllGenerations have been called to accelerate the aging process.)
   255      * 
   256      * NOTE: These bounds ignore delays in timer firings due to actual work being
   257      * performed by the browser. We use a slack timer so there is always at least
   258      * mTimerPeriod milliseconds between firings, which gives us (K-1)*mTimerPeriod
   259      * as a pretty solid lower bound. The upper bound is rather loose, however.
   260      * If the maximum amount by which any given timer firing is delayed is D, then
   261      * the upper bound before NotifyExpired is called is K*(mTimerPeriod + D).
   262      * 
   263      * The NotifyExpired call is expected to remove the object from the tracker,
   264      * but it need not. The object (or other objects) could be "resurrected"
   265      * by calling MarkUsed() on them, or they might just not be removed.
   266      * Any objects left over that have not been resurrected or removed
   267      * are placed in the new newest-generation, but this is considered "bad form"
   268      * and should be avoided (we'll issue a warning). (This recycling counts
   269      * as "a use" for the purposes of the expiry guarantee above...)
   270      * 
   271      * For robustness and simplicity, we allow objects to be notified more than
   272      * once here in the same timer tick.
   273      */
   274     virtual void NotifyExpired(T* aObj) = 0;
   276   private:
   277     class ExpirationTrackerObserver;
   278     nsRefPtr<ExpirationTrackerObserver> mObserver;
   279     nsTArray<T*>       mGenerations[K];
   280     nsCOMPtr<nsITimer> mTimer;
   281     uint32_t           mTimerPeriod;
   282     uint32_t           mNewestGeneration;
   283     bool               mInAgeOneGeneration;
   285     /**
   286      * Whenever "memory-pressure" is observed, it calls AgeAllGenerations()
   287      * to minimize memory usage.
   288      */
   289     class ExpirationTrackerObserver MOZ_FINAL : public nsIObserver {
   290     public:
   291       void Init(nsExpirationTracker<T,K> *obj) {
   292         mOwner = obj;
   293         nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   294         if (obs) {
   295           obs->AddObserver(this, "memory-pressure", false);
   296         }
   297       }
   298       void Destroy() {
   299         mOwner = nullptr;
   300         nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   301         if (obs)
   302           obs->RemoveObserver(this, "memory-pressure");
   303       }
   304       NS_DECL_ISUPPORTS
   305       NS_DECL_NSIOBSERVER
   306     private:
   307       nsExpirationTracker<T,K> *mOwner;
   308     };
   310     static void TimerCallback(nsITimer* aTimer, void* aThis) {
   311       nsExpirationTracker* tracker = static_cast<nsExpirationTracker*>(aThis);
   312       tracker->AgeOneGeneration();
   313       // Cancel the timer if we have no objects to track
   314       if (tracker->IsEmpty()) {
   315         tracker->mTimer->Cancel();
   316         tracker->mTimer = nullptr;
   317       }
   318     }
   320     nsresult CheckStartTimer() {
   321       if (mTimer || !mTimerPeriod)
   322         return NS_OK;
   323       mTimer = do_CreateInstance("@mozilla.org/timer;1");
   324       if (!mTimer)
   325         return NS_ERROR_OUT_OF_MEMORY;
   326       mTimer->InitWithFuncCallback(TimerCallback, this, mTimerPeriod,
   327                                    nsITimer::TYPE_REPEATING_SLACK);
   328       return NS_OK;
   329     }
   330 };
   332 template<class T, uint32_t K>
   333 NS_IMETHODIMP
   334 nsExpirationTracker<T, K>::ExpirationTrackerObserver::Observe(nsISupports     *aSubject,
   335                                                               const char      *aTopic,
   336                                                               const char16_t *aData)
   337 {
   338   if (!strcmp(aTopic, "memory-pressure") && mOwner)
   339     mOwner->AgeAllGenerations();
   340   return NS_OK;
   341 }
   343 template <class T, uint32_t K>
   344 NS_IMETHODIMP_(MozExternalRefCountType)
   345 nsExpirationTracker<T,K>::ExpirationTrackerObserver::AddRef(void)
   346 {
   347   MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
   348   NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
   349   ++mRefCnt;
   350   NS_LOG_ADDREF(this, mRefCnt, "ExpirationTrackerObserver", sizeof(*this));
   351   return mRefCnt;
   352 }
   354 template <class T, uint32_t K>
   355 NS_IMETHODIMP_(MozExternalRefCountType)
   356 nsExpirationTracker<T,K>::ExpirationTrackerObserver::Release(void)
   357 {
   358   MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
   359   NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
   360   --mRefCnt;
   361   NS_LOG_RELEASE(this, mRefCnt, "ExpirationTrackerObserver");
   362   if (mRefCnt == 0) {
   363     NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver);
   364     mRefCnt = 1; /* stabilize */
   365     delete (this);
   366     return 0;
   367   }
   368   return mRefCnt;
   369 }
   371 template <class T, uint32_t K>
   372 NS_IMETHODIMP
   373 nsExpirationTracker<T,K>::ExpirationTrackerObserver::QueryInterface(REFNSIID aIID, 
   374                                                                     void** aInstancePtr)
   375 {
   376   NS_ASSERTION(aInstancePtr,
   377                "QueryInterface requires a non-NULL destination!");            
   378   nsresult rv = NS_ERROR_FAILURE;
   379   NS_INTERFACE_TABLE(ExpirationTrackerObserver, nsIObserver)
   380   return rv;
   381 }
   383 #endif /*NSEXPIRATIONTRACKER_H_*/

mercurial