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