|
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "mozilla/MemoryReporting.h" |
|
8 #include "nsCache.h" |
|
9 #include <limits.h> |
|
10 |
|
11 #include "nscore.h" |
|
12 #include "nsDiskCacheBinding.h" |
|
13 #include "nsCacheService.h" |
|
14 |
|
15 |
|
16 |
|
17 /****************************************************************************** |
|
18 * static hash table callback functions |
|
19 * |
|
20 *****************************************************************************/ |
|
21 struct HashTableEntry : PLDHashEntryHdr { |
|
22 nsDiskCacheBinding * mBinding; |
|
23 }; |
|
24 |
|
25 |
|
26 static PLDHashNumber |
|
27 HashKey( PLDHashTable *table, const void *key) |
|
28 { |
|
29 return (PLDHashNumber) NS_PTR_TO_INT32(key); |
|
30 } |
|
31 |
|
32 |
|
33 static bool |
|
34 MatchEntry(PLDHashTable * /* table */, |
|
35 const PLDHashEntryHdr * header, |
|
36 const void * key) |
|
37 { |
|
38 HashTableEntry * hashEntry = (HashTableEntry *) header; |
|
39 return (hashEntry->mBinding->mRecord.HashNumber() == (PLDHashNumber) NS_PTR_TO_INT32(key)); |
|
40 } |
|
41 |
|
42 static void |
|
43 MoveEntry(PLDHashTable * /* table */, |
|
44 const PLDHashEntryHdr * src, |
|
45 PLDHashEntryHdr * dst) |
|
46 { |
|
47 ((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding; |
|
48 } |
|
49 |
|
50 |
|
51 static void |
|
52 ClearEntry(PLDHashTable * /* table */, |
|
53 PLDHashEntryHdr * header) |
|
54 { |
|
55 ((HashTableEntry *)header)->mBinding = nullptr; |
|
56 } |
|
57 |
|
58 |
|
59 /****************************************************************************** |
|
60 * Utility Functions |
|
61 *****************************************************************************/ |
|
62 nsDiskCacheBinding * |
|
63 GetCacheEntryBinding(nsCacheEntry * entry) |
|
64 { |
|
65 return (nsDiskCacheBinding *) entry->Data(); |
|
66 } |
|
67 |
|
68 |
|
69 /****************************************************************************** |
|
70 * nsDiskCacheBinding |
|
71 *****************************************************************************/ |
|
72 |
|
73 NS_IMPL_ISUPPORTS0(nsDiskCacheBinding) |
|
74 |
|
75 nsDiskCacheBinding::nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record) |
|
76 : mCacheEntry(entry) |
|
77 , mStreamIO(nullptr) |
|
78 , mDeactivateEvent(nullptr) |
|
79 { |
|
80 NS_ASSERTION(record->ValidRecord(), "bad record"); |
|
81 PR_INIT_CLIST(this); |
|
82 mRecord = *record; |
|
83 mDoomed = entry->IsDoomed(); |
|
84 mGeneration = record->Generation(); // 0 == uninitialized, or data & meta using block files |
|
85 } |
|
86 |
|
87 nsDiskCacheBinding::~nsDiskCacheBinding() |
|
88 { |
|
89 // Grab the cache lock since the binding is stored in nsCacheEntry::mData |
|
90 // and it is released using nsCacheService::ReleaseObject_Locked() which |
|
91 // releases the object outside the cache lock. |
|
92 nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEBINDING_DESTRUCTOR)); |
|
93 |
|
94 NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "binding deleted while still on list"); |
|
95 if (!PR_CLIST_IS_EMPTY(this)) |
|
96 PR_REMOVE_LINK(this); // XXX why are we still on a list? |
|
97 |
|
98 // sever streamIO/binding link |
|
99 if (mStreamIO) { |
|
100 if (NS_FAILED(mStreamIO->ClearBinding())) |
|
101 nsCacheService::DoomEntry(mCacheEntry); |
|
102 NS_RELEASE(mStreamIO); |
|
103 } |
|
104 } |
|
105 |
|
106 nsresult |
|
107 nsDiskCacheBinding::EnsureStreamIO() |
|
108 { |
|
109 if (!mStreamIO) { |
|
110 mStreamIO = new nsDiskCacheStreamIO(this); |
|
111 if (!mStreamIO) return NS_ERROR_OUT_OF_MEMORY; |
|
112 NS_ADDREF(mStreamIO); |
|
113 } |
|
114 return NS_OK; |
|
115 } |
|
116 |
|
117 |
|
118 /****************************************************************************** |
|
119 * nsDiskCacheBindery |
|
120 * |
|
121 * Keeps track of bound disk cache entries to detect for collisions. |
|
122 * |
|
123 *****************************************************************************/ |
|
124 |
|
125 const PLDHashTableOps nsDiskCacheBindery::ops = |
|
126 { |
|
127 PL_DHashAllocTable, |
|
128 PL_DHashFreeTable, |
|
129 HashKey, |
|
130 MatchEntry, |
|
131 MoveEntry, |
|
132 ClearEntry, |
|
133 PL_DHashFinalizeStub |
|
134 }; |
|
135 |
|
136 |
|
137 nsDiskCacheBindery::nsDiskCacheBindery() |
|
138 : initialized(false) |
|
139 { |
|
140 } |
|
141 |
|
142 |
|
143 nsDiskCacheBindery::~nsDiskCacheBindery() |
|
144 { |
|
145 Reset(); |
|
146 } |
|
147 |
|
148 |
|
149 nsresult |
|
150 nsDiskCacheBindery::Init() |
|
151 { |
|
152 nsresult rv = NS_OK; |
|
153 PL_DHashTableInit(&table, &ops, nullptr, sizeof(HashTableEntry), 0); |
|
154 initialized = true; |
|
155 |
|
156 return rv; |
|
157 } |
|
158 |
|
159 void |
|
160 nsDiskCacheBindery::Reset() |
|
161 { |
|
162 if (initialized) { |
|
163 PL_DHashTableFinish(&table); |
|
164 initialized = false; |
|
165 } |
|
166 } |
|
167 |
|
168 |
|
169 nsDiskCacheBinding * |
|
170 nsDiskCacheBindery::CreateBinding(nsCacheEntry * entry, |
|
171 nsDiskCacheRecord * record) |
|
172 { |
|
173 NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); |
|
174 nsCOMPtr<nsISupports> data = entry->Data(); |
|
175 if (data) { |
|
176 NS_ERROR("cache entry already has bind data"); |
|
177 return nullptr; |
|
178 } |
|
179 |
|
180 nsDiskCacheBinding * binding = new nsDiskCacheBinding(entry, record); |
|
181 if (!binding) return nullptr; |
|
182 |
|
183 // give ownership of the binding to the entry |
|
184 entry->SetData(binding); |
|
185 |
|
186 // add binding to collision detection system |
|
187 nsresult rv = AddBinding(binding); |
|
188 if (NS_FAILED(rv)) { |
|
189 entry->SetData(nullptr); |
|
190 return nullptr; |
|
191 } |
|
192 |
|
193 return binding; |
|
194 } |
|
195 |
|
196 |
|
197 /** |
|
198 * FindActiveEntry : to find active colliding entry so we can doom it |
|
199 */ |
|
200 nsDiskCacheBinding * |
|
201 nsDiskCacheBindery::FindActiveBinding(uint32_t hashNumber) |
|
202 { |
|
203 NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); |
|
204 // find hash entry for key |
|
205 HashTableEntry * hashEntry; |
|
206 hashEntry = |
|
207 (HashTableEntry *) PL_DHashTableOperate(&table, |
|
208 (void*)(uintptr_t) hashNumber, |
|
209 PL_DHASH_LOOKUP); |
|
210 if (PL_DHASH_ENTRY_IS_FREE(hashEntry)) return nullptr; |
|
211 |
|
212 // walk list looking for active entry |
|
213 NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding"); |
|
214 nsDiskCacheBinding * binding = hashEntry->mBinding; |
|
215 while (binding->mCacheEntry->IsDoomed()) { |
|
216 binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); |
|
217 if (binding == hashEntry->mBinding) return nullptr; |
|
218 } |
|
219 return binding; |
|
220 } |
|
221 |
|
222 |
|
223 /** |
|
224 * AddBinding |
|
225 * |
|
226 * Called from FindEntry() if we read an entry off of disk |
|
227 * - it may already have a generation number |
|
228 * - a generation number conflict is an error |
|
229 * |
|
230 * Called from BindEntry() |
|
231 * - a generation number needs to be assigned |
|
232 */ |
|
233 nsresult |
|
234 nsDiskCacheBindery::AddBinding(nsDiskCacheBinding * binding) |
|
235 { |
|
236 NS_ENSURE_ARG_POINTER(binding); |
|
237 NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); |
|
238 |
|
239 // find hash entry for key |
|
240 HashTableEntry * hashEntry; |
|
241 hashEntry = (HashTableEntry *) |
|
242 PL_DHashTableOperate(&table, |
|
243 (void *)(uintptr_t) binding->mRecord.HashNumber(), |
|
244 PL_DHASH_ADD); |
|
245 if (!hashEntry) return NS_ERROR_OUT_OF_MEMORY; |
|
246 |
|
247 if (hashEntry->mBinding == nullptr) { |
|
248 hashEntry->mBinding = binding; |
|
249 if (binding->mGeneration == 0) |
|
250 binding->mGeneration = 1; // if generation uninitialized, set it to 1 |
|
251 |
|
252 return NS_OK; |
|
253 } |
|
254 |
|
255 |
|
256 // insert binding in generation order |
|
257 nsDiskCacheBinding * p = hashEntry->mBinding; |
|
258 bool calcGeneration = (binding->mGeneration == 0); // do we need to calculate generation? |
|
259 if (calcGeneration) binding->mGeneration = 1; // initialize to 1 if uninitialized |
|
260 while (1) { |
|
261 |
|
262 if (binding->mGeneration < p->mGeneration) { |
|
263 // here we are |
|
264 PR_INSERT_BEFORE(binding, p); |
|
265 if (hashEntry->mBinding == p) |
|
266 hashEntry->mBinding = binding; |
|
267 break; |
|
268 } |
|
269 |
|
270 if (binding->mGeneration == p->mGeneration) { |
|
271 if (calcGeneration) ++binding->mGeneration; // try the next generation |
|
272 else { |
|
273 NS_ERROR("### disk cache: generations collide!"); |
|
274 return NS_ERROR_UNEXPECTED; |
|
275 } |
|
276 } |
|
277 |
|
278 p = (nsDiskCacheBinding *)PR_NEXT_LINK(p); |
|
279 if (p == hashEntry->mBinding) { |
|
280 // end of line: insert here or die |
|
281 p = (nsDiskCacheBinding *)PR_PREV_LINK(p); // back up and check generation |
|
282 if (p->mGeneration == 255) { |
|
283 NS_WARNING("### disk cache: generation capacity at full"); |
|
284 return NS_ERROR_UNEXPECTED; |
|
285 } |
|
286 PR_INSERT_BEFORE(binding, hashEntry->mBinding); |
|
287 break; |
|
288 } |
|
289 } |
|
290 return NS_OK; |
|
291 } |
|
292 |
|
293 |
|
294 /** |
|
295 * RemoveBinding : remove binding from collision detection on deactivation |
|
296 */ |
|
297 void |
|
298 nsDiskCacheBindery::RemoveBinding(nsDiskCacheBinding * binding) |
|
299 { |
|
300 NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); |
|
301 if (!initialized) return; |
|
302 |
|
303 HashTableEntry * hashEntry; |
|
304 void * key = (void *)(uintptr_t)binding->mRecord.HashNumber(); |
|
305 |
|
306 hashEntry = (HashTableEntry*) PL_DHashTableOperate(&table, |
|
307 (void*)(uintptr_t) key, |
|
308 PL_DHASH_LOOKUP); |
|
309 if (!PL_DHASH_ENTRY_IS_BUSY(hashEntry)) { |
|
310 NS_WARNING("### disk cache: binding not in hashtable!"); |
|
311 return; |
|
312 } |
|
313 |
|
314 if (binding == hashEntry->mBinding) { |
|
315 if (PR_CLIST_IS_EMPTY(binding)) { |
|
316 // remove this hash entry |
|
317 PL_DHashTableOperate(&table, |
|
318 (void*)(uintptr_t) binding->mRecord.HashNumber(), |
|
319 PL_DHASH_REMOVE); |
|
320 return; |
|
321 |
|
322 } else { |
|
323 // promote next binding to head, and unlink this binding |
|
324 hashEntry->mBinding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); |
|
325 } |
|
326 } |
|
327 PR_REMOVE_AND_INIT_LINK(binding); |
|
328 } |
|
329 |
|
330 |
|
331 /** |
|
332 * ActiveBinding : PLDHashTable enumerate function to verify active bindings |
|
333 */ |
|
334 |
|
335 PLDHashOperator |
|
336 ActiveBinding(PLDHashTable * table, |
|
337 PLDHashEntryHdr * hdr, |
|
338 uint32_t number, |
|
339 void * arg) |
|
340 { |
|
341 nsDiskCacheBinding * binding = ((HashTableEntry *)hdr)->mBinding; |
|
342 NS_ASSERTION(binding, "### disk cache binding = nullptr!"); |
|
343 |
|
344 nsDiskCacheBinding * head = binding; |
|
345 do { |
|
346 if (binding->IsActive()) { |
|
347 *((bool *)arg) = true; |
|
348 return PL_DHASH_STOP; |
|
349 } |
|
350 |
|
351 binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); |
|
352 } while (binding != head); |
|
353 |
|
354 return PL_DHASH_NEXT; |
|
355 } |
|
356 |
|
357 |
|
358 /** |
|
359 * ActiveBindings : return true if any bindings have open descriptors |
|
360 */ |
|
361 bool |
|
362 nsDiskCacheBindery::ActiveBindings() |
|
363 { |
|
364 NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); |
|
365 if (!initialized) return false; |
|
366 |
|
367 bool activeBinding = false; |
|
368 PL_DHashTableEnumerate(&table, ActiveBinding, &activeBinding); |
|
369 |
|
370 return activeBinding; |
|
371 } |
|
372 |
|
373 struct AccumulatorArg { |
|
374 size_t mUsage; |
|
375 mozilla::MallocSizeOf mMallocSizeOf; |
|
376 }; |
|
377 |
|
378 PLDHashOperator |
|
379 AccumulateHeapUsage(PLDHashTable *table, PLDHashEntryHdr *hdr, uint32_t number, |
|
380 void *arg) |
|
381 { |
|
382 nsDiskCacheBinding *binding = ((HashTableEntry *)hdr)->mBinding; |
|
383 NS_ASSERTION(binding, "### disk cache binding = nsnull!"); |
|
384 |
|
385 AccumulatorArg *acc = (AccumulatorArg *)arg; |
|
386 |
|
387 nsDiskCacheBinding *head = binding; |
|
388 do { |
|
389 acc->mUsage += acc->mMallocSizeOf(binding); |
|
390 |
|
391 if (binding->mStreamIO) { |
|
392 acc->mUsage += binding->mStreamIO->SizeOfIncludingThis(acc->mMallocSizeOf); |
|
393 } |
|
394 |
|
395 /* No good way to get at mDeactivateEvent internals for proper size, so |
|
396 we use this as an estimate. */ |
|
397 if (binding->mDeactivateEvent) { |
|
398 acc->mUsage += acc->mMallocSizeOf(binding->mDeactivateEvent); |
|
399 } |
|
400 |
|
401 binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); |
|
402 } while (binding != head); |
|
403 |
|
404 return PL_DHASH_NEXT; |
|
405 } |
|
406 |
|
407 /** |
|
408 * SizeOfExcludingThis: return the amount of heap memory (bytes) being used by the bindery |
|
409 */ |
|
410 size_t |
|
411 nsDiskCacheBindery::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) |
|
412 { |
|
413 NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); |
|
414 if (!initialized) return 0; |
|
415 |
|
416 AccumulatorArg arg; |
|
417 arg.mUsage = 0; |
|
418 arg.mMallocSizeOf = aMallocSizeOf; |
|
419 |
|
420 PL_DHashTableEnumerate(&table, AccumulateHeapUsage, &arg); |
|
421 |
|
422 return arg.mUsage; |
|
423 } |