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