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