Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "CacheLog.h"
6 #include "CacheFileContextEvictor.h"
7 #include "CacheFileIOManager.h"
8 #include "CacheIndex.h"
9 #include "CacheIndexIterator.h"
10 #include "CacheFileUtils.h"
11 #include "nsIFile.h"
12 #include "LoadContextInfo.h"
13 #include "nsThreadUtils.h"
14 #include "nsString.h"
15 #include "nsISimpleEnumerator.h"
16 #include "nsIDirectoryEnumerator.h"
17 #include "mozilla/Base64.h"
20 namespace mozilla {
21 namespace net {
23 const char kContextEvictionPrefix[] = "ce_";
24 const uint32_t kContextEvictionPrefixLength =
25 sizeof(kContextEvictionPrefix) - 1;
27 bool CacheFileContextEvictor::sDiskAlreadySearched = false;
29 CacheFileContextEvictor::CacheFileContextEvictor()
30 : mEvicting(false)
31 , mIndexIsUpToDate(false)
32 {
33 LOG(("CacheFileContextEvictor::CacheFileContextEvictor() [this=%p]", this));
34 }
36 CacheFileContextEvictor::~CacheFileContextEvictor()
37 {
38 LOG(("CacheFileContextEvictor::~CacheFileContextEvictor() [this=%p]", this));
39 }
41 nsresult
42 CacheFileContextEvictor::Init(nsIFile *aCacheDirectory)
43 {
44 LOG(("CacheFileContextEvictor::Init()"));
46 nsresult rv;
48 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
50 CacheIndex::IsUpToDate(&mIndexIsUpToDate);
52 mCacheDirectory = aCacheDirectory;
54 rv = aCacheDirectory->Clone(getter_AddRefs(mEntriesDir));
55 if (NS_WARN_IF(NS_FAILED(rv))) {
56 return rv;
57 }
59 rv = mEntriesDir->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
60 if (NS_WARN_IF(NS_FAILED(rv))) {
61 return rv;
62 }
64 if (!sDiskAlreadySearched) {
65 LoadEvictInfoFromDisk();
66 if ((mEntries.Length() != 0) && mIndexIsUpToDate) {
67 CreateIterators();
68 StartEvicting();
69 }
70 }
72 return NS_OK;
73 }
75 uint32_t
76 CacheFileContextEvictor::ContextsCount()
77 {
78 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
80 return mEntries.Length();
81 }
83 nsresult
84 CacheFileContextEvictor::AddContext(nsILoadContextInfo *aLoadContextInfo)
85 {
86 LOG(("CacheFileContextEvictor::AddContext() [this=%p, loadContextInfo=%p]",
87 this, aLoadContextInfo));
89 nsresult rv;
91 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
93 CacheFileContextEvictorEntry *entry = nullptr;
94 for (uint32_t i = 0; i < mEntries.Length(); ++i) {
95 if (mEntries[i]->mInfo->Equals(aLoadContextInfo)) {
96 entry = mEntries[i];
97 break;
98 }
99 }
101 if (!entry) {
102 entry = new CacheFileContextEvictorEntry();
103 entry->mInfo = aLoadContextInfo;
104 mEntries.AppendElement(entry);
105 }
107 entry->mTimeStamp = PR_Now() / PR_USEC_PER_MSEC;
109 PersistEvictionInfoToDisk(aLoadContextInfo);
111 if (mIndexIsUpToDate) {
112 // Already existing context could be added again, in this case the iterator
113 // would be recreated. Close the old iterator explicitely.
114 if (entry->mIterator) {
115 entry->mIterator->Close();
116 entry->mIterator = nullptr;
117 }
119 rv = CacheIndex::GetIterator(aLoadContextInfo, false,
120 getter_AddRefs(entry->mIterator));
121 if (NS_FAILED(rv)) {
122 // This could probably happen during shutdown. Remove the entry from
123 // the array, but leave the info on the disk. No entry can be opened
124 // during shutdown and we'll load the eviction info on next start.
125 LOG(("CacheFileContextEvictor::AddContext() - Cannot get an iterator. "
126 "[rv=0x%08x]", rv));
127 mEntries.RemoveElement(entry);
128 return rv;
129 }
131 StartEvicting();
132 }
134 return NS_OK;
135 }
137 nsresult
138 CacheFileContextEvictor::CacheIndexStateChanged()
139 {
140 LOG(("CacheFileContextEvictor::CacheIndexStateChanged() [this=%p]", this));
142 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
144 bool isUpToDate = false;
145 CacheIndex::IsUpToDate(&isUpToDate);
146 if (mEntries.Length() == 0) {
147 // Just save the state and exit, since there is nothing to do
148 mIndexIsUpToDate = isUpToDate;
149 return NS_OK;
150 }
152 if (!isUpToDate && !mIndexIsUpToDate) {
153 // Index is outdated and status has not changed, nothing to do.
154 return NS_OK;
155 }
157 if (isUpToDate && mIndexIsUpToDate) {
158 // Status has not changed, but make sure the eviction is running.
159 if (mEvicting) {
160 return NS_OK;
161 }
163 // We're not evicting, but we should be evicting?!
164 LOG(("CacheFileContextEvictor::CacheIndexStateChanged() - Index is up to "
165 "date, we have some context to evict but eviction is not running! "
166 "Starting now."));
167 }
169 mIndexIsUpToDate = isUpToDate;
171 if (mIndexIsUpToDate) {
172 CreateIterators();
173 StartEvicting();
174 } else {
175 CloseIterators();
176 }
178 return NS_OK;
179 }
181 nsresult
182 CacheFileContextEvictor::WasEvicted(const nsACString &aKey, nsIFile *aFile,
183 bool *_retval)
184 {
185 LOG(("CacheFileContextEvictor::WasEvicted() [key=%s]",
186 PromiseFlatCString(aKey).get()));
188 nsresult rv;
190 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
192 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
193 MOZ_ASSERT(info);
194 if (!info) {
195 LOG(("CacheFileContextEvictor::WasEvicted() - Cannot parse key!"));
196 *_retval = false;
197 return NS_OK;
198 }
200 CacheFileContextEvictorEntry *entry = nullptr;
201 for (uint32_t i = 0; i < mEntries.Length(); ++i) {
202 if (info->Equals(mEntries[i]->mInfo)) {
203 entry = mEntries[i];
204 break;
205 }
206 }
208 if (!entry) {
209 LOG(("CacheFileContextEvictor::WasEvicted() - Didn't find equal context, "
210 "returning false."));
211 *_retval = false;
212 return NS_OK;
213 }
215 PRTime lastModifiedTime;
216 rv = aFile->GetLastModifiedTime(&lastModifiedTime);
217 if (NS_FAILED(rv)) {
218 LOG(("CacheFileContextEvictor::WasEvicted() - Cannot get last modified time"
219 ", returning false."));
220 *_retval = false;
221 return NS_OK;
222 }
224 *_retval = !(lastModifiedTime > entry->mTimeStamp);
225 LOG(("CacheFileContextEvictor::WasEvicted() - returning %s. [mTimeStamp=%lld,"
226 " lastModifiedTime=%lld]", *_retval ? "true" : "false",
227 mEntries[0]->mTimeStamp, lastModifiedTime));
229 return NS_OK;
230 }
232 nsresult
233 CacheFileContextEvictor::PersistEvictionInfoToDisk(
234 nsILoadContextInfo *aLoadContextInfo)
235 {
236 LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() [this=%p, "
237 "loadContextInfo=%p]", this, aLoadContextInfo));
239 nsresult rv;
241 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
243 nsCOMPtr<nsIFile> file;
244 rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
245 if (NS_WARN_IF(NS_FAILED(rv))) {
246 return rv;
247 }
249 #ifdef PR_LOGGING
250 nsAutoCString path;
251 file->GetNativePath(path);
252 #endif
254 PRFileDesc *fd;
255 rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600,
256 &fd);
257 if (NS_WARN_IF(NS_FAILED(rv))) {
258 LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Creating file "
259 "failed! [path=%s, rv=0x%08x]", path.get(), rv));
260 return rv;
261 }
263 PR_Close(fd);
265 LOG(("CacheFileContextEvictor::PersistEvictionInfoToDisk() - Successfully "
266 "created file. [path=%s]", path.get()));
268 return NS_OK;
269 }
271 nsresult
272 CacheFileContextEvictor::RemoveEvictInfoFromDisk(
273 nsILoadContextInfo *aLoadContextInfo)
274 {
275 LOG(("CacheFileContextEvictor::RemoveEvictInfoFromDisk() [this=%p, "
276 "loadContextInfo=%p]", this, aLoadContextInfo));
278 nsresult rv;
280 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
282 nsCOMPtr<nsIFile> file;
283 rv = GetContextFile(aLoadContextInfo, getter_AddRefs(file));
284 if (NS_WARN_IF(NS_FAILED(rv))) {
285 return rv;
286 }
288 #ifdef PR_LOGGING
289 nsAutoCString path;
290 file->GetNativePath(path);
291 #endif
293 rv = file->Remove(false);
294 if (NS_WARN_IF(NS_FAILED(rv))) {
295 LOG(("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Removing file"
296 " failed! [path=%s, rv=0x%08x]", path.get(), rv));
297 return rv;
298 }
300 LOG(("CacheFileContextEvictor::RemoveEvictionInfoFromDisk() - Successfully "
301 "removed file. [path=%s]", path.get()));
303 return NS_OK;
304 }
306 nsresult
307 CacheFileContextEvictor::LoadEvictInfoFromDisk()
308 {
309 LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() [this=%p]", this));
311 nsresult rv;
313 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
315 sDiskAlreadySearched = true;
317 nsCOMPtr<nsISimpleEnumerator> enumerator;
318 rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(enumerator));
319 if (NS_WARN_IF(NS_FAILED(rv))) {
320 return rv;
321 }
323 nsCOMPtr<nsIDirectoryEnumerator> dirEnum = do_QueryInterface(enumerator, &rv);
324 if (NS_WARN_IF(NS_FAILED(rv))) {
325 return rv;
326 }
328 while (true) {
329 nsCOMPtr<nsIFile> file;
330 rv = dirEnum->GetNextFile(getter_AddRefs(file));
331 if (!file) {
332 break;
333 }
335 bool isDir = false;
336 file->IsDirectory(&isDir);
337 if (isDir) {
338 continue;
339 }
341 nsAutoCString leaf;
342 rv = file->GetNativeLeafName(leaf);
343 if (NS_FAILED(rv)) {
344 LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - "
345 "GetNativeLeafName() failed! Skipping file."));
346 continue;
347 }
349 if (leaf.Length() < kContextEvictionPrefixLength) {
350 continue;
351 }
353 if (!StringBeginsWith(leaf, NS_LITERAL_CSTRING(kContextEvictionPrefix))) {
354 continue;
355 }
357 nsAutoCString encoded;
358 encoded = Substring(leaf, kContextEvictionPrefixLength);
359 encoded.ReplaceChar('-', '/');
361 nsAutoCString decoded;
362 rv = Base64Decode(encoded, decoded);
363 if (NS_FAILED(rv)) {
364 LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Base64 decoding "
365 "failed. Removing the file. [file=%s]", leaf.get()));
366 file->Remove(false);
367 continue;
368 }
370 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(decoded);
372 if (!info) {
373 LOG(("CacheFileContextEvictor::LoadEvictInfoFromDisk() - Cannot parse "
374 "context key, removing file. [contextKey=%s, file=%s]",
375 decoded.get(), leaf.get()));
376 file->Remove(false);
377 continue;
378 }
380 PRTime lastModifiedTime;
381 rv = file->GetLastModifiedTime(&lastModifiedTime);
382 if (NS_FAILED(rv)) {
383 continue;
384 }
386 CacheFileContextEvictorEntry *entry = new CacheFileContextEvictorEntry();
387 entry->mInfo = info;
388 entry->mTimeStamp = lastModifiedTime;
389 mEntries.AppendElement(entry);
390 }
392 return NS_OK;
393 }
395 nsresult
396 CacheFileContextEvictor::GetContextFile(nsILoadContextInfo *aLoadContextInfo,
397 nsIFile **_retval)
398 {
399 nsresult rv;
401 nsAutoCString leafName;
402 leafName.Assign(NS_LITERAL_CSTRING(kContextEvictionPrefix));
404 nsAutoCString keyPrefix;
405 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, keyPrefix);
407 // TODO: This hack is needed because current CacheFileUtils::ParseKey() can
408 // parse only the whole key and not just the key prefix generated by
409 // CacheFileUtils::CreateKeyPrefix(). This should be removed once bug #968593
410 // is fixed.
411 keyPrefix.Append(":foo");
413 nsAutoCString data64;
414 rv = Base64Encode(keyPrefix, data64);
415 if (NS_WARN_IF(NS_FAILED(rv))) {
416 return rv;
417 }
419 // Replace '/' with '-' since '/' cannot be part of the filename.
420 data64.ReplaceChar('/', '-');
422 leafName.Append(data64);
424 nsCOMPtr<nsIFile> file;
425 rv = mCacheDirectory->Clone(getter_AddRefs(file));
426 if (NS_WARN_IF(NS_FAILED(rv))) {
427 return rv;
428 }
430 rv = file->AppendNative(leafName);
431 if (NS_WARN_IF(NS_FAILED(rv))) {
432 return rv;
433 }
435 file.swap(*_retval);
436 return NS_OK;
437 }
439 void
440 CacheFileContextEvictor::CreateIterators()
441 {
442 LOG(("CacheFileContextEvictor::CreateIterators() [this=%p]", this));
444 CloseIterators();
446 nsresult rv;
448 for (uint32_t i = 0; i < mEntries.Length(); ) {
449 rv = CacheIndex::GetIterator(mEntries[i]->mInfo, false,
450 getter_AddRefs(mEntries[i]->mIterator));
451 if (NS_FAILED(rv)) {
452 LOG(("CacheFileContextEvictor::CreateIterators() - Cannot get an iterator"
453 ". [rv=0x%08x]", rv));
454 mEntries.RemoveElementAt(i);
455 continue;
456 }
458 ++i;
459 }
460 }
462 void
463 CacheFileContextEvictor::CloseIterators()
464 {
465 LOG(("CacheFileContextEvictor::CloseIterators() [this=%p]", this));
467 for (uint32_t i = 0; i < mEntries.Length(); ++i) {
468 if (mEntries[i]->mIterator) {
469 mEntries[i]->mIterator->Close();
470 mEntries[i]->mIterator = nullptr;
471 }
472 }
473 }
475 void
476 CacheFileContextEvictor::StartEvicting()
477 {
478 LOG(("CacheFileContextEvictor::StartEvicting() [this=%p]", this));
480 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
482 if (mEvicting) {
483 LOG(("CacheFileContextEvictor::StartEvicting() - already evicintg."));
484 return;
485 }
487 if (mEntries.Length() == 0) {
488 LOG(("CacheFileContextEvictor::StartEvicting() - no context to evict."));
489 return;
490 }
492 nsCOMPtr<nsIRunnable> ev;
493 ev = NS_NewRunnableMethod(this, &CacheFileContextEvictor::EvictEntries);
495 nsRefPtr<CacheIOThread> ioThread = CacheFileIOManager::IOThread();
497 nsresult rv = ioThread->Dispatch(ev, CacheIOThread::EVICT);
498 if (NS_FAILED(rv)) {
499 LOG(("CacheFileContextEvictor::StartEvicting() - Cannot dispatch event to "
500 "IO thread. [rv=0x%08x]", rv));
501 }
503 mEvicting = true;
504 }
506 nsresult
507 CacheFileContextEvictor::EvictEntries()
508 {
509 LOG(("CacheFileContextEvictor::EvictEntries()"));
511 nsresult rv;
513 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
515 mEvicting = false;
517 if (!mIndexIsUpToDate) {
518 LOG(("CacheFileContextEvictor::EvictEntries() - Stopping evicting due to "
519 "outdated index."));
520 return NS_OK;
521 }
523 while (true) {
524 if (CacheIOThread::YieldAndRerun()) {
525 LOG(("CacheFileContextEvictor::EvictEntries() - Breaking loop for higher "
526 "level events."));
527 mEvicting = true;
528 return NS_OK;
529 }
531 if (mEntries.Length() == 0) {
532 LOG(("CacheFileContextEvictor::EvictEntries() - Stopping evicting, there "
533 "is no context to evict."));
534 return NS_OK;
535 }
537 SHA1Sum::Hash hash;
538 rv = mEntries[0]->mIterator->GetNextHash(&hash);
539 if (rv == NS_ERROR_NOT_AVAILABLE) {
540 LOG(("CacheFileContextEvictor::EvictEntries() - No more entries left in "
541 "iterator. [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
542 mEntries[0]->mInfo.get()));
543 RemoveEvictInfoFromDisk(mEntries[0]->mInfo);
544 mEntries.RemoveElementAt(0);
545 continue;
546 } else if (NS_FAILED(rv)) {
547 LOG(("CacheFileContextEvictor::EvictEntries() - Iterator failed to "
548 "provide next hash (shutdown?), keeping eviction info on disk."
549 " [iterator=%p, info=%p]", mEntries[0]->mIterator.get(),
550 mEntries[0]->mInfo.get()));
551 mEntries.RemoveElementAt(0);
552 continue;
553 }
555 LOG(("CacheFileContextEvictor::EvictEntries() - Processing hash. "
556 "[hash=%08x%08x%08x%08x%08x, iterator=%p, info=%p]", LOGSHA1(&hash),
557 mEntries[0]->mIterator.get(), mEntries[0]->mInfo.get()));
559 nsRefPtr<CacheFileHandle> handle;
560 CacheFileIOManager::gInstance->mHandles.GetHandle(&hash, false,
561 getter_AddRefs(handle));
562 if (handle) {
563 // We doom any active handle in CacheFileIOManager::EvictByContext(), so
564 // this must be a new one. Skip it.
565 LOG(("CacheFileContextEvictor::EvictEntries() - Skipping entry since we "
566 "found an active handle. [handle=%p]", handle.get()));
567 continue;
568 }
570 nsAutoCString leafName;
571 CacheFileIOManager::HashToStr(&hash, leafName);
573 PRTime lastModifiedTime;
574 nsCOMPtr<nsIFile> file;
575 rv = mEntriesDir->Clone(getter_AddRefs(file));
576 if (NS_SUCCEEDED(rv)) {
577 rv = file->AppendNative(leafName);
578 }
579 if (NS_SUCCEEDED(rv)) {
580 rv = file->GetLastModifiedTime(&lastModifiedTime);
581 }
582 if (NS_FAILED(rv)) {
583 LOG(("CacheFileContextEvictor::EvictEntries() - Cannot get last modified "
584 "time, skipping entry."));
585 continue;
586 }
588 if (lastModifiedTime > mEntries[0]->mTimeStamp) {
589 LOG(("CacheFileContextEvictor::EvictEntries() - Skipping newer entry. "
590 "[mTimeStamp=%lld, lastModifiedTime=%lld]", mEntries[0]->mTimeStamp,
591 lastModifiedTime));
592 continue;
593 }
595 LOG(("CacheFileContextEvictor::EvictEntries - Removing entry."));
596 file->Remove(false);
597 CacheIndex::RemoveEntry(&hash);
598 }
600 NS_NOTREACHED("We should never get here");
601 return NS_OK;
602 }
604 } // net
605 } // mozilla