LCOV - code coverage report
Current view: directory - objdir/dist/include - nsExpirationTracker.h (source / functions) Found Hit Coverage
Test: app.info Lines: 138 105 76.1 %
Date: 2012-06-02 Functions: 140 40 28.6 %

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

Generated by: LCOV version 1.7