Bug 564076: Small parser cleanup changes. (r=mrbkap)
[mozilla-central.git] / xpcom / ds / nsExpirationTracker.h
blob1dcca72d7eacdb0eb99ee85cb964a8ca5733992f
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
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/
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.
15 * The Original Code is mozilla.org code.
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.
22 * Contributor(s):
23 * robert@ocallahan.org
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.
37 * ***** END LICENSE BLOCK ***** */
39 #ifndef NSEXPIRATIONTRACKER_H_
40 #define NSEXPIRATIONTRACKER_H_
42 #include "prlog.h"
43 #include "nsTArray.h"
44 #include "nsITimer.h"
45 #include "nsCOMPtr.h"
46 #include "nsComponentManagerUtils.h"
48 /**
49 * Data used to track the expiration state of an object. We promise that this
50 * is 32 bits so that objects that includes this as a field can pad and align
51 * efficiently.
53 struct nsExpirationState {
54 enum { NOT_TRACKED = (1U << 4) - 1,
55 MAX_INDEX_IN_GENERATION = (1U << 28) - 1 };
57 nsExpirationState() : mGeneration(NOT_TRACKED) {}
58 PRBool IsTracked() { return mGeneration != NOT_TRACKED; }
60 /**
61 * The generation that this object belongs to, or NOT_TRACKED.
63 PRUint32 mGeneration:4;
64 PRUint32 mIndexInGeneration:28;
67 /**
68 * nsExpirationTracker can track the lifetimes and usage of a large number of
69 * objects, and send a notification some window of time after a live object was
70 * last used. This is very useful when you manage a large number of objects
71 * and want to flush some after they haven't been used for a while.
72 * nsExpirationTracker is designed to be very space and time efficient.
74 * The type parameter T is the object type that we will track pointers to. T
75 * must include an accessible method GetExpirationState() that returns a
76 * pointer to an nsExpirationState associated with the object (preferably,
77 * stored in a field of the object).
79 * The parameter K is the number of generations that will be used. Increasing
80 * the number of generations narrows the window within which we promise
81 * to fire notifications, at a slight increase in space cost for the tracker.
82 * We require 2 <= K <= nsExpirationState::NOT_TRACKED (currently 15).
84 * To use this class, you need to inherit from it and override the
85 * NotifyExpired() method.
87 * The approach is to track objects in K generations. When an object is accessed
88 * it moves from its current generation to the newest generation. Generations
89 * are stored in a cyclic array; when a timer interrupt fires, we advance
90 * the current generation pointer to effectively age all objects very efficiently.
91 * By storing information in each object about its generation and index within its
92 * generation array, we make removal of objects from a generation very cheap.
94 * Future work:
95 * -- Add a method to change the timer period?
97 template <class T, PRUint32 K> class nsExpirationTracker {
98 public:
99 /**
100 * Initialize the tracker.
101 * @param aTimerPeriod the timer period in milliseconds. The guarantees
102 * provided by the tracker are defined in terms of this period. If the
103 * period is zero, then we don't use a timer and rely on someone calling
104 * AgeOneGeneration explicitly.
106 nsExpirationTracker(PRUint32 aTimerPeriod)
107 : mTimerPeriod(aTimerPeriod), mNewestGeneration(0),
108 mInAgeOneGeneration(PR_FALSE) {
109 PR_STATIC_ASSERT(K >= 2 && K <= nsExpirationState::NOT_TRACKED);
111 ~nsExpirationTracker() {
112 if (mTimer) {
113 mTimer->Cancel();
118 * Add an object to be tracked. It must not already be tracked. It will
119 * be added to the newest generation, i.e., as if it was just used.
120 * @return an error on out-of-memory
122 nsresult AddObject(T* aObj) {
123 nsExpirationState* state = aObj->GetExpirationState();
124 NS_ASSERTION(!state->IsTracked(), "Tried to add an object that's already tracked");
125 nsTArray<T*>& generation = mGenerations[mNewestGeneration];
126 PRUint32 index = generation.Length();
127 if (index > nsExpirationState::MAX_INDEX_IN_GENERATION) {
128 NS_WARNING("More than 256M elements tracked, this is probably a problem");
129 return NS_ERROR_OUT_OF_MEMORY;
131 if (index == 0) {
132 // We might need to start the timer
133 nsresult rv = CheckStartTimer();
134 if (NS_FAILED(rv))
135 return rv;
137 if (!generation.AppendElement(aObj))
138 return NS_ERROR_OUT_OF_MEMORY;
139 state->mGeneration = mNewestGeneration;
140 state->mIndexInGeneration = index;
141 return NS_OK;
145 * Remove an object from the tracker. It must currently be tracked.
147 void RemoveObject(T* aObj) {
148 nsExpirationState* state = aObj->GetExpirationState();
149 NS_ASSERTION(state->IsTracked(), "Tried to remove an object that's not tracked");
150 nsTArray<T*>& generation = mGenerations[state->mGeneration];
151 PRUint32 index = state->mIndexInGeneration;
152 NS_ASSERTION(generation.Length() > index &&
153 generation[index] == aObj, "Object is lying about its index");
154 // Move the last object to fill the hole created by removing aObj
155 PRUint32 last = generation.Length() - 1;
156 T* lastObj = generation[last];
157 generation[index] = lastObj;
158 lastObj->GetExpirationState()->mIndexInGeneration = index;
159 generation.RemoveElementAt(last);
160 state->mGeneration = nsExpirationState::NOT_TRACKED;
161 // We do not check whether we need to stop the timer here. The timer
162 // will check that itself next time it fires. Checking here would not
163 // be efficient since we'd need to track all generations. Also we could
164 // thrash by incessantly creating and destroying timers if someone
165 // kept adding and removing an object from the tracker.
169 * Notify that an object has been used.
170 * @return an error if we lost the object from the tracker...
172 nsresult MarkUsed(T* aObj) {
173 nsExpirationState* state = aObj->GetExpirationState();
174 if (mNewestGeneration == state->mGeneration)
175 return NS_OK;
176 RemoveObject(aObj);
177 return AddObject(aObj);
181 * The timer calls this, but it can also be manually called if you want
182 * to age objects "artifically". This can result in calls to NotifyExpired.
184 void AgeOneGeneration() {
185 if (mInAgeOneGeneration) {
186 NS_WARNING("Can't reenter AgeOneGeneration from NotifyExpired");
187 return;
190 mInAgeOneGeneration = PR_TRUE;
191 PRUint32 reapGeneration =
192 mNewestGeneration > 0 ? mNewestGeneration - 1 : K - 1;
193 nsTArray<T*>& generation = mGenerations[reapGeneration];
194 // The following is rather tricky. We have to cope with objects being
195 // removed from this generation either because of a call to RemoveObject
196 // (or indirectly via MarkUsed) inside NotifyExpired. Fortunately no
197 // objects can be added to this generation because it's not the newest
198 // generation. We depend on the fact that RemoveObject can only cause
199 // the indexes of objects in this generation to *decrease*, not increase.
200 // So if we start from the end and work our way backwards we are guaranteed
201 // to see each object at least once.
202 PRUint32 index = generation.Length();
203 for (;;) {
204 // Objects could have been removed so index could be outside
205 // the array
206 index = PR_MIN(index, generation.Length());
207 if (index == 0)
208 break;
209 --index;
210 NotifyExpired(generation[index]);
212 // Any leftover objects from reapGeneration just end up in the new
213 // newest-generation. This is bad form, though, so warn if there are any.
214 if (!generation.IsEmpty()) {
215 NS_WARNING("Expired objects were not removed or marked used");
217 // Free excess memory used by the generation array, since we probably
218 // just removed most or all of its elements.
219 generation.Compact();
220 mNewestGeneration = reapGeneration;
221 mInAgeOneGeneration = PR_FALSE;
225 * This just calls AgeOneGeneration K times. Under normal circumstances this
226 * will result in all objects getting NotifyExpired called on them, but
227 * if NotifyExpired itself marks some objects as used, then those objects
228 * might not expire. This would be a good thing to call if we get into
229 * a critically-low memory situation.
231 void AgeAllGenerations() {
232 PRUint32 i;
233 for (i = 0; i < K; ++i) {
234 AgeOneGeneration();
238 class Iterator {
239 private:
240 nsExpirationTracker<T,K>* mTracker;
241 PRUint32 mGeneration;
242 PRUint32 mIndex;
243 public:
244 Iterator(nsExpirationTracker<T,K>* aTracker)
245 : mTracker(aTracker), mGeneration(0), mIndex(0) {}
246 T* Next() {
247 while (mGeneration < K) {
248 nsTArray<T*>* generation = &mTracker->mGenerations[mGeneration];
249 if (mIndex < generation->Length()) {
250 ++mIndex;
251 return (*generation)[mIndex - 1];
253 ++mGeneration;
254 mIndex = 0;
256 return nsnull;
260 friend class Iterator;
262 protected:
264 * This must be overridden to catch notifications. It is called whenever
265 * we detect that an object has not been used for at least (K-1)*mTimerPeriod
266 * seconds. If timer events are not delayed, it will be called within
267 * roughly K*mTimerPeriod seconds after the last use. (Unless AgeOneGeneration
268 * or AgeAllGenerations have been called to accelerate the aging process.)
270 * NOTE: These bounds ignore delays in timer firings due to actual work being
271 * performed by the browser. We use a slack timer so there is always at least
272 * mTimerPeriod milliseconds between firings, which gives us (K-1)*mTimerPeriod
273 * as a pretty solid lower bound. The upper bound is rather loose, however.
274 * If the maximum amount by which any given timer firing is delayed is D, then
275 * the upper bound before NotifyExpired is called is K*(mTimerPeriod + D).
277 * The NotifyExpired call is expected to remove the object from the tracker,
278 * but it need not. The object (or other objects) could be "resurrected"
279 * by calling MarkUsed() on them, or they might just not be removed.
280 * Any objects left over that have not been resurrected or removed
281 * are placed in the new newest-generation, but this is considered "bad form"
282 * and should be avoided (we'll issue a warning). (This recycling counts
283 * as "a use" for the purposes of the expiry guarantee above...)
285 * For robustness and simplicity, we allow objects to be notified more than
286 * once here in the same timer tick.
288 virtual void NotifyExpired(T* aObj) = 0;
290 private:
291 nsTArray<T*> mGenerations[K];
292 nsCOMPtr<nsITimer> mTimer;
293 PRUint32 mTimerPeriod;
294 PRUint32 mNewestGeneration;
295 PRPackedBool mInAgeOneGeneration;
297 static void TimerCallback(nsITimer* aTimer, void* aThis) {
298 nsExpirationTracker* tracker = static_cast<nsExpirationTracker*>(aThis);
299 tracker->AgeOneGeneration();
300 // Cancel the timer if we have no objects to track
301 PRUint32 i;
302 for (i = 0; i < K; ++i) {
303 if (!tracker->mGenerations[i].IsEmpty())
304 return;
306 tracker->mTimer->Cancel();
307 tracker->mTimer = nsnull;
310 nsresult CheckStartTimer() {
311 if (mTimer || !mTimerPeriod)
312 return NS_OK;
313 mTimer = do_CreateInstance("@mozilla.org/timer;1");
314 if (!mTimer)
315 return NS_ERROR_OUT_OF_MEMORY;
316 mTimer->InitWithFuncCallback(TimerCallback, this, mTimerPeriod,
317 nsITimer::TYPE_REPEATING_SLACK);
318 return NS_OK;
322 #endif /*NSEXPIRATIONTRACKER_H_*/