Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 #ifndef CacheIndex__h__
6 #define CacheIndex__h__
8 #include "CacheLog.h"
9 #include "CacheFileIOManager.h"
10 #include "nsIRunnable.h"
11 #include "CacheHashUtils.h"
12 #include "nsICacheStorageService.h"
13 #include "nsICacheEntry.h"
14 #include "nsILoadContextInfo.h"
15 #include "nsTHashtable.h"
16 #include "nsThreadUtils.h"
17 #include "nsWeakReference.h"
18 #include "mozilla/SHA1.h"
19 #include "mozilla/Mutex.h"
20 #include "mozilla/Endian.h"
21 #include "mozilla/TimeStamp.h"
23 class nsIFile;
24 class nsIDirectoryEnumerator;
25 class nsITimer;
28 #ifdef DEBUG
29 #define DEBUG_STATS 1
30 #endif
32 namespace mozilla {
33 namespace net {
35 class CacheFileMetadata;
36 class FileOpenHelper;
37 class CacheIndexIterator;
39 typedef struct {
40 // Version of the index. The index must be ignored and deleted when the file
41 // on disk was written with a newer version.
42 uint32_t mVersion;
44 // Timestamp of time when the last successful write of the index started.
45 // During update process we use this timestamp for a quick validation of entry
46 // files. If last modified time of the file is lower than this timestamp, we
47 // skip parsing of such file since the information in index should be up to
48 // date.
49 uint32_t mTimeStamp;
51 // We set this flag as soon as possible after parsing index during startup
52 // and clean it after we write journal to disk during shutdown. We ignore the
53 // journal and start update process whenever this flag is set during index
54 // parsing.
55 uint32_t mIsDirty;
56 } CacheIndexHeader;
58 struct CacheIndexRecord {
59 SHA1Sum::Hash mHash;
60 uint32_t mFrecency;
61 uint32_t mExpirationTime;
62 uint32_t mAppId;
64 /*
65 * 1000 0000 0000 0000 0000 0000 0000 0000 : initialized
66 * 0100 0000 0000 0000 0000 0000 0000 0000 : anonymous
67 * 0010 0000 0000 0000 0000 0000 0000 0000 : inBrowser
68 * 0001 0000 0000 0000 0000 0000 0000 0000 : removed
69 * 0000 1000 0000 0000 0000 0000 0000 0000 : dirty
70 * 0000 0100 0000 0000 0000 0000 0000 0000 : fresh
71 * 0000 0011 0000 0000 0000 0000 0000 0000 : reserved
72 * 0000 0000 1111 1111 1111 1111 1111 1111 : file size (in kB)
73 */
74 uint32_t mFlags;
76 CacheIndexRecord()
77 : mFrecency(0)
78 , mExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
79 , mAppId(nsILoadContextInfo::NO_APP_ID)
80 , mFlags(0)
81 {}
82 };
84 class CacheIndexEntry : public PLDHashEntryHdr
85 {
86 public:
87 typedef const SHA1Sum::Hash& KeyType;
88 typedef const SHA1Sum::Hash* KeyTypePointer;
90 CacheIndexEntry(KeyTypePointer aKey)
91 {
92 MOZ_COUNT_CTOR(CacheIndexEntry);
93 mRec = new CacheIndexRecord();
94 LOG(("CacheIndexEntry::CacheIndexEntry() - Created record [rec=%p]", mRec.get()));
95 memcpy(&mRec->mHash, aKey, sizeof(SHA1Sum::Hash));
96 }
97 CacheIndexEntry(const CacheIndexEntry& aOther)
98 {
99 NS_NOTREACHED("CacheIndexEntry copy constructor is forbidden!");
100 }
101 ~CacheIndexEntry()
102 {
103 MOZ_COUNT_DTOR(CacheIndexEntry);
104 LOG(("CacheIndexEntry::~CacheIndexEntry() - Deleting record [rec=%p]",
105 mRec.get()));
106 }
108 // KeyEquals(): does this entry match this key?
109 bool KeyEquals(KeyTypePointer aKey) const
110 {
111 return memcmp(&mRec->mHash, aKey, sizeof(SHA1Sum::Hash)) == 0;
112 }
114 // KeyToPointer(): Convert KeyType to KeyTypePointer
115 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
117 // HashKey(): calculate the hash number
118 static PLDHashNumber HashKey(KeyTypePointer aKey)
119 {
120 return (reinterpret_cast<const uint32_t *>(aKey))[0];
121 }
123 // ALLOW_MEMMOVE can we move this class with memmove(), or do we have
124 // to use the copy constructor?
125 enum { ALLOW_MEMMOVE = true };
127 bool operator==(const CacheIndexEntry& aOther) const
128 {
129 return KeyEquals(&aOther.mRec->mHash);
130 }
132 CacheIndexEntry& operator=(const CacheIndexEntry& aOther)
133 {
134 MOZ_ASSERT(memcmp(&mRec->mHash, &aOther.mRec->mHash,
135 sizeof(SHA1Sum::Hash)) == 0);
136 mRec->mFrecency = aOther.mRec->mFrecency;
137 mRec->mExpirationTime = aOther.mRec->mExpirationTime;
138 mRec->mAppId = aOther.mRec->mAppId;
139 mRec->mFlags = aOther.mRec->mFlags;
140 return *this;
141 }
143 void InitNew()
144 {
145 mRec->mFrecency = 0;
146 mRec->mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
147 mRec->mAppId = nsILoadContextInfo::NO_APP_ID;
148 mRec->mFlags = 0;
149 }
151 void Init(uint32_t aAppId, bool aAnonymous, bool aInBrowser)
152 {
153 MOZ_ASSERT(mRec->mFrecency == 0);
154 MOZ_ASSERT(mRec->mExpirationTime == nsICacheEntry::NO_EXPIRATION_TIME);
155 MOZ_ASSERT(mRec->mAppId == nsILoadContextInfo::NO_APP_ID);
156 // When we init the entry it must be fresh and may be dirty
157 MOZ_ASSERT((mRec->mFlags & ~kDirtyMask) == kFreshMask);
159 mRec->mAppId = aAppId;
160 mRec->mFlags |= kInitializedMask;
161 if (aAnonymous) {
162 mRec->mFlags |= kAnonymousMask;
163 }
164 if (aInBrowser) {
165 mRec->mFlags |= kInBrowserMask;
166 }
167 }
169 const SHA1Sum::Hash * Hash() { return &mRec->mHash; }
171 bool IsInitialized() { return !!(mRec->mFlags & kInitializedMask); }
173 uint32_t AppId() { return mRec->mAppId; }
174 bool Anonymous() { return !!(mRec->mFlags & kAnonymousMask); }
175 bool InBrowser() { return !!(mRec->mFlags & kInBrowserMask); }
177 bool IsRemoved() { return !!(mRec->mFlags & kRemovedMask); }
178 void MarkRemoved() { mRec->mFlags |= kRemovedMask; }
180 bool IsDirty() { return !!(mRec->mFlags & kDirtyMask); }
181 void MarkDirty() { mRec->mFlags |= kDirtyMask; }
182 void ClearDirty() { mRec->mFlags &= ~kDirtyMask; }
184 bool IsFresh() { return !!(mRec->mFlags & kFreshMask); }
185 void MarkFresh() { mRec->mFlags |= kFreshMask; }
187 void SetFrecency(uint32_t aFrecency) { mRec->mFrecency = aFrecency; }
188 uint32_t GetFrecency() { return mRec->mFrecency; }
190 void SetExpirationTime(uint32_t aExpirationTime)
191 {
192 mRec->mExpirationTime = aExpirationTime;
193 }
194 uint32_t GetExpirationTime() { return mRec->mExpirationTime; }
196 // Sets filesize in kilobytes.
197 void SetFileSize(uint32_t aFileSize)
198 {
199 if (aFileSize > kFileSizeMask) {
200 LOG(("CacheIndexEntry::SetFileSize() - FileSize is too large, "
201 "truncating to %u", kFileSizeMask));
202 aFileSize = kFileSizeMask;
203 }
204 mRec->mFlags &= ~kFileSizeMask;
205 mRec->mFlags |= aFileSize;
206 }
207 // Returns filesize in kilobytes.
208 uint32_t GetFileSize() { return mRec->mFlags & kFileSizeMask; }
209 bool IsFileEmpty() { return GetFileSize() == 0; }
211 void WriteToBuf(void *aBuf)
212 {
213 CacheIndexRecord *dst = reinterpret_cast<CacheIndexRecord *>(aBuf);
215 // Copy the whole record to the buffer.
216 memcpy(aBuf, mRec, sizeof(CacheIndexRecord));
218 // Dirty and fresh flags should never go to disk, since they make sense only
219 // during current session.
220 dst->mFlags &= ~kDirtyMask;
221 dst->mFlags &= ~kFreshMask;
223 #if defined(IS_LITTLE_ENDIAN)
224 // Data in the buffer are in machine byte order and we want them in network
225 // byte order.
226 NetworkEndian::writeUint32(&dst->mFrecency, dst->mFrecency);
227 NetworkEndian::writeUint32(&dst->mExpirationTime, dst->mExpirationTime);
228 NetworkEndian::writeUint32(&dst->mAppId, dst->mAppId);
229 NetworkEndian::writeUint32(&dst->mFlags, dst->mFlags);
230 #endif
231 }
233 void ReadFromBuf(void *aBuf)
234 {
235 CacheIndexRecord *src= reinterpret_cast<CacheIndexRecord *>(aBuf);
236 MOZ_ASSERT(memcmp(&mRec->mHash, &src->mHash,
237 sizeof(SHA1Sum::Hash)) == 0);
239 mRec->mFrecency = NetworkEndian::readUint32(&src->mFrecency);
240 mRec->mExpirationTime = NetworkEndian::readUint32(&src->mExpirationTime);
241 mRec->mAppId = NetworkEndian::readUint32(&src->mAppId);
242 mRec->mFlags = NetworkEndian::readUint32(&src->mFlags);
243 }
245 void Log() {
246 LOG(("CacheIndexEntry::Log() [this=%p, hash=%08x%08x%08x%08x%08x, fresh=%u,"
247 " initialized=%u, removed=%u, dirty=%u, anonymous=%u, inBrowser=%u, "
248 "appId=%u, frecency=%u, expirationTime=%u, size=%u]",
249 this, LOGSHA1(mRec->mHash), IsFresh(), IsInitialized(), IsRemoved(),
250 IsDirty(), Anonymous(), InBrowser(), AppId(), GetFrecency(),
251 GetExpirationTime(), GetFileSize()));
252 }
254 static bool RecordMatchesLoadContextInfo(CacheIndexRecord *aRec,
255 nsILoadContextInfo *aInfo)
256 {
257 if (!aInfo->IsPrivate() &&
258 aInfo->AppId() == aRec->mAppId &&
259 aInfo->IsAnonymous() == !!(aRec->mFlags & kAnonymousMask) &&
260 aInfo->IsInBrowserElement() == !!(aRec->mFlags & kInBrowserMask)) {
261 return true;
262 }
264 return false;
265 }
267 // Memory reporting
268 size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
269 {
270 return mallocSizeOf(mRec.get());
271 }
273 size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
274 {
275 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
276 }
278 private:
279 friend class CacheIndex;
280 friend class CacheIndexEntryAutoManage;
282 static const uint32_t kInitializedMask = 0x80000000;
283 static const uint32_t kAnonymousMask = 0x40000000;
284 static const uint32_t kInBrowserMask = 0x20000000;
286 // This flag is set when the entry was removed. We need to keep this
287 // information in memory until we write the index file.
288 static const uint32_t kRemovedMask = 0x10000000;
290 // This flag is set when the information in memory is not in sync with the
291 // information in index file on disk.
292 static const uint32_t kDirtyMask = 0x08000000;
294 // This flag is set when the information about the entry is fresh, i.e.
295 // we've created or opened this entry during this session, or we've seen
296 // this entry during update or build process.
297 static const uint32_t kFreshMask = 0x04000000;
299 static const uint32_t kReservedMask = 0x03000000;
301 // FileSize in kilobytes
302 static const uint32_t kFileSizeMask = 0x00FFFFFF;
304 nsAutoPtr<CacheIndexRecord> mRec;
305 };
307 class CacheIndexStats
308 {
309 public:
310 CacheIndexStats()
311 : mCount(0)
312 , mNotInitialized(0)
313 , mRemoved(0)
314 , mDirty(0)
315 , mFresh(0)
316 , mEmpty(0)
317 , mSize(0)
318 #ifdef DEBUG
319 , mStateLogged(false)
320 , mDisableLogging(false)
321 #endif
322 {
323 }
325 bool operator==(const CacheIndexStats& aOther) const
326 {
327 return
328 #ifdef DEBUG
329 aOther.mStateLogged == mStateLogged &&
330 #endif
331 aOther.mCount == mCount &&
332 aOther.mNotInitialized == mNotInitialized &&
333 aOther.mRemoved == mRemoved &&
334 aOther.mDirty == mDirty &&
335 aOther.mFresh == mFresh &&
336 aOther.mEmpty == mEmpty &&
337 aOther.mSize == mSize;
338 }
340 #ifdef DEBUG
341 void DisableLogging() {
342 mDisableLogging = true;
343 }
344 #endif
346 void Log() {
347 LOG(("CacheIndexStats::Log() [count=%u, notInitialized=%u, removed=%u, "
348 "dirty=%u, fresh=%u, empty=%u, size=%u]", mCount, mNotInitialized,
349 mRemoved, mDirty, mFresh, mEmpty, mSize));
350 }
352 void Clear() {
353 MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Clear() - state logged!");
355 mCount = 0;
356 mNotInitialized = 0;
357 mRemoved = 0;
358 mDirty = 0;
359 mFresh = 0;
360 mEmpty = 0;
361 mSize = 0;
362 }
364 #ifdef DEBUG
365 bool StateLogged() {
366 return mStateLogged;
367 }
368 #endif
370 uint32_t Count() {
371 MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Count() - state logged!");
372 return mCount;
373 }
375 uint32_t Dirty() {
376 MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Dirty() - state logged!");
377 return mDirty;
378 }
380 uint32_t Fresh() {
381 MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Fresh() - state logged!");
382 return mFresh;
383 }
385 uint32_t ActiveEntriesCount() {
386 MOZ_ASSERT(!mStateLogged, "CacheIndexStats::ActiveEntriesCount() - state "
387 "logged!");
388 return mCount - mRemoved - mNotInitialized - mEmpty;
389 }
391 uint32_t Size() {
392 MOZ_ASSERT(!mStateLogged, "CacheIndexStats::Size() - state logged!");
393 return mSize;
394 }
396 void BeforeChange(CacheIndexEntry *aEntry) {
397 #ifdef DEBUG_STATS
398 if (!mDisableLogging) {
399 LOG(("CacheIndexStats::BeforeChange()"));
400 Log();
401 }
402 #endif
404 MOZ_ASSERT(!mStateLogged, "CacheIndexStats::BeforeChange() - state "
405 "logged!");
406 #ifdef DEBUG
407 mStateLogged = true;
408 #endif
409 if (aEntry) {
410 MOZ_ASSERT(mCount);
411 mCount--;
412 if (aEntry->IsDirty()) {
413 MOZ_ASSERT(mDirty);
414 mDirty--;
415 }
416 if (aEntry->IsFresh()) {
417 MOZ_ASSERT(mFresh);
418 mFresh--;
419 }
420 if (aEntry->IsRemoved()) {
421 MOZ_ASSERT(mRemoved);
422 mRemoved--;
423 } else {
424 if (!aEntry->IsInitialized()) {
425 MOZ_ASSERT(mNotInitialized);
426 mNotInitialized--;
427 } else {
428 if (aEntry->IsFileEmpty()) {
429 MOZ_ASSERT(mEmpty);
430 mEmpty--;
431 } else {
432 MOZ_ASSERT(mSize >= aEntry->GetFileSize());
433 mSize -= aEntry->GetFileSize();
434 }
435 }
436 }
437 }
438 }
440 void AfterChange(CacheIndexEntry *aEntry) {
441 MOZ_ASSERT(mStateLogged, "CacheIndexStats::AfterChange() - state not "
442 "logged!");
443 #ifdef DEBUG
444 mStateLogged = false;
445 #endif
446 if (aEntry) {
447 ++mCount;
448 if (aEntry->IsDirty()) {
449 mDirty++;
450 }
451 if (aEntry->IsFresh()) {
452 mFresh++;
453 }
454 if (aEntry->IsRemoved()) {
455 mRemoved++;
456 } else {
457 if (!aEntry->IsInitialized()) {
458 mNotInitialized++;
459 } else {
460 if (aEntry->IsFileEmpty()) {
461 mEmpty++;
462 } else {
463 mSize += aEntry->GetFileSize();
464 }
465 }
466 }
467 }
469 #ifdef DEBUG_STATS
470 if (!mDisableLogging) {
471 LOG(("CacheIndexStats::AfterChange()"));
472 Log();
473 }
474 #endif
475 }
477 private:
478 uint32_t mCount;
479 uint32_t mNotInitialized;
480 uint32_t mRemoved;
481 uint32_t mDirty;
482 uint32_t mFresh;
483 uint32_t mEmpty;
484 uint32_t mSize;
485 #ifdef DEBUG
486 // We completely remove the data about an entry from the stats in
487 // BeforeChange() and set this flag to true. The entry is then modified,
488 // deleted or created and the data is again put into the stats and this flag
489 // set to false. Statistics must not be read during this time since the
490 // information is not correct.
491 bool mStateLogged;
493 // Disables logging in this instance of CacheIndexStats
494 bool mDisableLogging;
495 #endif
496 };
498 class CacheIndex : public CacheFileIOListener
499 , public nsIRunnable
500 {
501 public:
502 NS_DECL_THREADSAFE_ISUPPORTS
503 NS_DECL_NSIRUNNABLE
505 CacheIndex();
507 static nsresult Init(nsIFile *aCacheDirectory);
508 static nsresult PreShutdown();
509 static nsresult Shutdown();
511 // Following methods can be called only on IO thread.
513 // Add entry to the index. The entry shouldn't be present in index. This
514 // method is called whenever a new handle for a new entry file is created. The
515 // newly created entry is not initialized and it must be either initialized
516 // with InitEntry() or removed with RemoveEntry().
517 static nsresult AddEntry(const SHA1Sum::Hash *aHash);
519 // Inform index about an existing entry that should be present in index. This
520 // method is called whenever a new handle for an existing entry file is
521 // created. Like in case of AddEntry(), either InitEntry() or RemoveEntry()
522 // must be called on the entry, since the entry is not initizlized if the
523 // index is outdated.
524 static nsresult EnsureEntryExists(const SHA1Sum::Hash *aHash);
526 // Initialize the entry. It MUST be present in index. Call to AddEntry() or
527 // EnsureEntryExists() must precede the call to this method.
528 static nsresult InitEntry(const SHA1Sum::Hash *aHash,
529 uint32_t aAppId,
530 bool aAnonymous,
531 bool aInBrowser);
533 // Remove entry from index. The entry should be present in index.
534 static nsresult RemoveEntry(const SHA1Sum::Hash *aHash);
536 // Update some information in entry. The entry MUST be present in index and
537 // MUST be initialized. Call to AddEntry() or EnsureEntryExists() and to
538 // InitEntry() must precede the call to this method.
539 // Pass nullptr if the value didn't change.
540 static nsresult UpdateEntry(const SHA1Sum::Hash *aHash,
541 const uint32_t *aFrecency,
542 const uint32_t *aExpirationTime,
543 const uint32_t *aSize);
545 // Remove all entries from the index. Called when clearing the whole cache.
546 static nsresult RemoveAll();
548 enum EntryStatus {
549 EXISTS = 0,
550 DOES_NOT_EXIST = 1,
551 DO_NOT_KNOW = 2
552 };
554 // Returns status of the entry in index for the given key. It can be called
555 // on any thread.
556 static nsresult HasEntry(const nsACString &aKey, EntryStatus *_retval);
558 // Returns a hash of the least important entry that should be evicted if the
559 // cache size is over limit and also returns a total number of all entries in
560 // the index.
561 static nsresult GetEntryForEviction(SHA1Sum::Hash *aHash, uint32_t *aCnt);
563 // Returns cache size in kB.
564 static nsresult GetCacheSize(uint32_t *_retval);
566 // Asynchronously gets the disk cache size, used for display in the UI.
567 static nsresult AsyncGetDiskConsumption(nsICacheStorageConsumptionObserver* aObserver);
569 // Returns an iterator that returns entries matching a given context that were
570 // present in the index at the time this method was called. If aAddNew is true
571 // then the iterator will also return entries created after this call.
572 // NOTE: When some entry is removed from index it is removed also from the
573 // iterator regardless what aAddNew was passed.
574 static nsresult GetIterator(nsILoadContextInfo *aInfo, bool aAddNew,
575 CacheIndexIterator **_retval);
577 // Returns true if we _think_ that the index is up to date. I.e. the state is
578 // READY or WRITING and mIndexNeedsUpdate as well as mShuttingDown is false.
579 static nsresult IsUpToDate(bool *_retval);
581 // Memory reporting
582 static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
583 static size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
585 private:
586 friend class CacheIndexEntryAutoManage;
587 friend class CacheIndexAutoLock;
588 friend class CacheIndexAutoUnlock;
589 friend class FileOpenHelper;
590 friend class CacheIndexIterator;
592 virtual ~CacheIndex();
594 NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult);
595 nsresult OnFileOpenedInternal(FileOpenHelper *aOpener,
596 CacheFileHandle *aHandle, nsresult aResult);
597 NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
598 nsresult aResult);
599 NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult);
600 NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
601 NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult);
602 NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult);
604 void Lock();
605 void Unlock();
606 void AssertOwnsLock();
608 nsresult InitInternal(nsIFile *aCacheDirectory);
609 void PreShutdownInternal();
611 // This method returns false when index is not initialized or is shut down.
612 bool IsIndexUsable();
614 // This method checks whether the entry has the same values of appId,
615 // isAnonymous and isInBrowser. We don't expect to find a collision since
616 // these values are part of the key that we hash and we use a strong hash
617 // function.
618 static bool IsCollision(CacheIndexEntry *aEntry,
619 uint32_t aAppId,
620 bool aAnonymous,
621 bool aInBrowser);
623 // Checks whether any of the information about the entry has changed.
624 static bool HasEntryChanged(CacheIndexEntry *aEntry,
625 const uint32_t *aFrecency,
626 const uint32_t *aExpirationTime,
627 const uint32_t *aSize);
629 // Merge all pending operations from mPendingUpdates into mIndex.
630 void ProcessPendingOperations();
631 static PLDHashOperator UpdateEntryInIndex(CacheIndexEntry *aEntry,
632 void* aClosure);
634 // Following methods perform writing of the index file.
635 //
636 // The index is written periodically, but not earlier than once in
637 // kMinDumpInterval and there must be at least kMinUnwrittenChanges
638 // differences between index on disk and in memory. Index is always first
639 // written to a temporary file and the old index file is replaced when the
640 // writing process succeeds.
641 //
642 // Starts writing of index when both limits (minimal delay between writes and
643 // minimum number of changes in index) were exceeded.
644 bool WriteIndexToDiskIfNeeded();
645 // Starts writing of index file.
646 void WriteIndexToDisk();
647 // Serializes part of mIndex hashtable to the write buffer a writes the buffer
648 // to the file.
649 void WriteRecords();
650 // Finalizes writing process.
651 void FinishWrite(bool aSucceeded);
653 static PLDHashOperator CopyRecordsToRWBuf(CacheIndexEntry *aEntry,
654 void* aClosure);
655 static PLDHashOperator ApplyIndexChanges(CacheIndexEntry *aEntry,
656 void* aClosure);
658 // Following methods perform writing of the journal during shutdown. All these
659 // methods must be called only during shutdown since they write/delete files
660 // directly on the main thread instead of using CacheFileIOManager that does
661 // it asynchronously on IO thread. Journal contains only entries that are
662 // dirty, i.e. changes that are not present in the index file on the disk.
663 // When the log is written successfully, the dirty flag in index file is
664 // cleared.
665 nsresult GetFile(const nsACString &aName, nsIFile **_retval);
666 nsresult RemoveFile(const nsACString &aName);
667 void RemoveIndexFromDisk();
668 // Writes journal to the disk and clears dirty flag in index header.
669 nsresult WriteLogToDisk();
671 static PLDHashOperator WriteEntryToLog(CacheIndexEntry *aEntry,
672 void* aClosure);
674 // Following methods perform reading of the index from the disk.
675 //
676 // Index is read at startup just after initializing the CacheIndex. There are
677 // 3 files used when manipulating with index: index file, journal file and
678 // a temporary file. All files contain the hash of the data, so we can check
679 // whether the content is valid and complete. Index file contains also a dirty
680 // flag in the index header which is unset on a clean shutdown. During opening
681 // and reading of the files we determine the status of the whole index from
682 // the states of the separate files. Following table shows all possible
683 // combinations:
684 //
685 // index, journal, tmpfile
686 // M * * - index is missing -> BUILD
687 // I * * - index is invalid -> BUILD
688 // D * * - index is dirty -> UPDATE
689 // C M * - index is dirty -> UPDATE
690 // C I * - unexpected state -> UPDATE
691 // C V E - unexpected state -> UPDATE
692 // C V M - index is up to date -> READY
693 //
694 // where the letters mean:
695 // * - any state
696 // E - file exists
697 // M - file is missing
698 // I - data is invalid (parsing failed or hash didn't match)
699 // D - dirty (data in index file is correct, but dirty flag is set)
700 // C - clean (index file is clean)
701 // V - valid (data in journal file is correct)
702 //
703 // Note: We accept the data from journal only when the index is up to date as
704 // a whole (i.e. C,V,M state).
705 //
706 // We rename the journal file to the temporary file as soon as possible after
707 // initial test to ensure that we start update process on the next startup if
708 // FF crashes during parsing of the index.
709 //
710 // Initiates reading index from disk.
711 void ReadIndexFromDisk();
712 // Starts reading data from index file.
713 void StartReadingIndex();
714 // Parses data read from index file.
715 void ParseRecords();
716 // Starts reading data from journal file.
717 void StartReadingJournal();
718 // Parses data read from journal file.
719 void ParseJournal();
720 // Merges entries from journal into mIndex.
721 void MergeJournal();
722 // In debug build this method checks that we have no fresh entry in mIndex
723 // after we finish reading index and before we process pending operations.
724 void EnsureNoFreshEntry();
725 // In debug build this method is called after processing pending operations
726 // to make sure mIndexStats contains correct information.
727 void EnsureCorrectStats();
728 static PLDHashOperator SumIndexStats(CacheIndexEntry *aEntry, void* aClosure);
729 // Finalizes reading process.
730 void FinishRead(bool aSucceeded);
732 static PLDHashOperator ProcessJournalEntry(CacheIndexEntry *aEntry,
733 void* aClosure);
735 // Following methods perform updating and building of the index.
736 // Timer callback that starts update or build process.
737 static void DelayedUpdate(nsITimer *aTimer, void *aClosure);
738 // Posts timer event that start update or build process.
739 nsresult ScheduleUpdateTimer(uint32_t aDelay);
740 nsresult SetupDirectoryEnumerator();
741 void InitEntryFromDiskData(CacheIndexEntry *aEntry,
742 CacheFileMetadata *aMetaData,
743 int64_t aFileSize);
744 // Returns true when either a timer is scheduled or event is posted.
745 bool IsUpdatePending();
746 // Iterates through all files in entries directory that we didn't create/open
747 // during this session, parses them and adds the entries to the index.
748 void BuildIndex();
750 bool StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState = false);
751 // Starts update or build process or fires a timer when it is too early after
752 // startup.
753 void StartUpdatingIndex(bool aRebuild);
754 // Iterates through all files in entries directory that we didn't create/open
755 // during this session and theirs last modified time is newer than timestamp
756 // in the index header. Parses the files and adds the entries to the index.
757 void UpdateIndex();
758 // Finalizes update or build process.
759 void FinishUpdate(bool aSucceeded);
761 static PLDHashOperator RemoveNonFreshEntries(CacheIndexEntry *aEntry,
762 void* aClosure);
764 enum EState {
765 // Initial state in which the index is not usable
766 // Possible transitions:
767 // -> READING
768 INITIAL = 0,
770 // Index is being read from the disk.
771 // Possible transitions:
772 // -> INITIAL - We failed to dispatch a read event.
773 // -> BUILDING - No or corrupted index file was found.
774 // -> UPDATING - No or corrupted journal file was found.
775 // - Dirty flag was set in index header.
776 // -> READY - Index was read successfully or was interrupted by
777 // pre-shutdown.
778 // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
779 READING = 1,
781 // Index is being written to the disk.
782 // Possible transitions:
783 // -> READY - Writing of index finished or was interrupted by
784 // pre-shutdown..
785 // -> UPDATING - Writing of index finished, but index was found outdated
786 // during writing.
787 // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
788 WRITING = 2,
790 // Index is being build.
791 // Possible transitions:
792 // -> READY - Building of index finished or was interrupted by
793 // pre-shutdown.
794 // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
795 BUILDING = 3,
797 // Index is being updated.
798 // Possible transitions:
799 // -> READY - Updating of index finished or was interrupted by
800 // pre-shutdown.
801 // -> SHUTDOWN - This could happen only in case of pre-shutdown failure.
802 UPDATING = 4,
804 // Index is ready.
805 // Possible transitions:
806 // -> UPDATING - Index was found outdated.
807 // -> SHUTDOWN - Index is shutting down.
808 READY = 5,
810 // Index is shutting down.
811 SHUTDOWN = 6
812 };
814 #ifdef PR_LOGGING
815 static char const * StateString(EState aState);
816 #endif
817 void ChangeState(EState aNewState);
819 // Allocates and releases buffer used for reading and writing index.
820 void AllocBuffer();
821 void ReleaseBuffer();
823 // Methods used by CacheIndexEntryAutoManage to keep the arrays up to date.
824 void InsertRecordToFrecencyArray(CacheIndexRecord *aRecord);
825 void InsertRecordToExpirationArray(CacheIndexRecord *aRecord);
826 void RemoveRecordFromFrecencyArray(CacheIndexRecord *aRecord);
827 void RemoveRecordFromExpirationArray(CacheIndexRecord *aRecord);
829 // Methods used by CacheIndexEntryAutoManage to keep the iterators up to date.
830 void AddRecordToIterators(CacheIndexRecord *aRecord);
831 void RemoveRecordFromIterators(CacheIndexRecord *aRecord);
832 void ReplaceRecordInIterators(CacheIndexRecord *aOldRecord,
833 CacheIndexRecord *aNewRecord);
835 // Memory reporting (private part)
836 size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
838 static CacheIndex *gInstance;
840 nsCOMPtr<nsIFile> mCacheDirectory;
842 mozilla::Mutex mLock;
843 EState mState;
844 // Timestamp of time when the index was initialized. We use it to delay
845 // initial update or build of index.
846 TimeStamp mStartTime;
847 // Set to true in PreShutdown(), it is checked on variaous places to prevent
848 // starting any process (write, update, etc.) during shutdown.
849 bool mShuttingDown;
850 // When set to true, update process should start as soon as possible. This
851 // flag is set whenever we find some inconsistency which would be fixed by
852 // update process. The flag is checked always when switching to READY state.
853 // To make sure we start the update process as soon as possible, methods that
854 // set this flag should also call StartUpdatingIndexIfNeeded() to cover the
855 // case when we are currently in READY state.
856 bool mIndexNeedsUpdate;
857 // Set at the beginning of RemoveAll() which clears the whole index. When
858 // removing all entries we must stop any pending reading, writing, updating or
859 // building operation. This flag is checked at various places and it prevents
860 // we won't start another operation (e.g. canceling reading of the index would
861 // normally start update or build process)
862 bool mRemovingAll;
863 // Whether the index file on disk exists and is valid.
864 bool mIndexOnDiskIsValid;
865 // When something goes wrong during updating or building process, we don't
866 // mark index clean (and also don't write journal) to ensure that update or
867 // build will be initiated on the next start.
868 bool mDontMarkIndexClean;
869 // Timestamp value from index file. It is used during update process to skip
870 // entries that were last modified before this timestamp.
871 uint32_t mIndexTimeStamp;
872 // Timestamp of last time the index was dumped to disk.
873 // NOTE: The index might not be necessarily dumped at this time. The value
874 // is used to schedule next dump of the index.
875 TimeStamp mLastDumpTime;
877 // Timer of delayed update/build.
878 nsCOMPtr<nsITimer> mUpdateTimer;
879 // True when build or update event is posted
880 bool mUpdateEventPending;
882 // Helper members used when reading/writing index from/to disk.
883 // Contains number of entries that should be skipped:
884 // - in hashtable when writing index because they were already written
885 // - in index file when reading index because they were already read
886 uint32_t mSkipEntries;
887 // Number of entries that should be written to disk. This is number of entries
888 // in hashtable that are initialized and are not marked as removed when writing
889 // begins.
890 uint32_t mProcessEntries;
891 char *mRWBuf;
892 uint32_t mRWBufSize;
893 uint32_t mRWBufPos;
894 nsRefPtr<CacheHash> mRWHash;
896 // Reading of journal succeeded if true.
897 bool mJournalReadSuccessfully;
899 // Handle used for writing and reading index file.
900 nsRefPtr<CacheFileHandle> mIndexHandle;
901 // Handle used for reading journal file.
902 nsRefPtr<CacheFileHandle> mJournalHandle;
903 // Used to check the existence of the file during reading process.
904 nsRefPtr<CacheFileHandle> mTmpHandle;
906 nsRefPtr<FileOpenHelper> mIndexFileOpener;
907 nsRefPtr<FileOpenHelper> mJournalFileOpener;
908 nsRefPtr<FileOpenHelper> mTmpFileOpener;
910 // Directory enumerator used when building and updating index.
911 nsCOMPtr<nsIDirectoryEnumerator> mDirEnumerator;
913 // Main index hashtable.
914 nsTHashtable<CacheIndexEntry> mIndex;
916 // We cannot add, remove or change any entry in mIndex in states READING and
917 // WRITING. We track all changes in mPendingUpdates during these states.
918 nsTHashtable<CacheIndexEntry> mPendingUpdates;
920 // Contains information statistics for mIndex + mPendingUpdates.
921 CacheIndexStats mIndexStats;
923 // When reading journal, we must first parse the whole file and apply the
924 // changes iff the journal was read successfully. mTmpJournal is used to store
925 // entries from the journal file. We throw away all these entries if parsing
926 // of the journal fails or the hash does not match.
927 nsTHashtable<CacheIndexEntry> mTmpJournal;
929 // Arrays that keep entry records ordered by eviction preference. When looking
930 // for an entry to evict, we first try to find an expired entry. If there is
931 // no expired entry, we take the entry with lowest valid frecency. Zero
932 // frecency is an initial value and such entries are stored at the end of the
933 // array. Uninitialized entries and entries marked as deleted are not present
934 // in these arrays.
935 nsTArray<CacheIndexRecord *> mFrecencyArray;
936 nsTArray<CacheIndexRecord *> mExpirationArray;
938 nsTArray<CacheIndexIterator *> mIterators;
940 class DiskConsumptionObserver : public nsRunnable
941 {
942 public:
943 static DiskConsumptionObserver* Init(nsICacheStorageConsumptionObserver* aObserver)
944 {
945 nsWeakPtr observer = do_GetWeakReference(aObserver);
946 if (!observer)
947 return nullptr;
949 return new DiskConsumptionObserver(observer);
950 }
952 void OnDiskConsumption(int64_t aSize)
953 {
954 mSize = aSize;
955 NS_DispatchToMainThread(this);
956 }
958 private:
959 DiskConsumptionObserver(nsWeakPtr const &aWeakObserver)
960 : mObserver(aWeakObserver) { }
961 virtual ~DiskConsumptionObserver() { }
963 NS_IMETHODIMP Run()
964 {
965 MOZ_ASSERT(NS_IsMainThread());
967 nsCOMPtr<nsICacheStorageConsumptionObserver> observer =
968 do_QueryReferent(mObserver);
970 if (observer) {
971 observer->OnNetworkCacheDiskConsumption(mSize);
972 }
974 return NS_OK;
975 }
977 nsWeakPtr mObserver;
978 int64_t mSize;
979 };
981 // List of async observers that want to get disk consumption information
982 nsTArray<nsRefPtr<DiskConsumptionObserver> > mDiskConsumptionObservers;
983 };
985 class CacheIndexAutoLock {
986 public:
987 CacheIndexAutoLock(CacheIndex *aIndex)
988 : mIndex(aIndex)
989 , mLocked(true)
990 {
991 mIndex->Lock();
992 }
993 ~CacheIndexAutoLock()
994 {
995 if (mLocked) {
996 mIndex->Unlock();
997 }
998 }
999 void Lock()
1000 {
1001 MOZ_ASSERT(!mLocked);
1002 mIndex->Lock();
1003 mLocked = true;
1004 }
1005 void Unlock()
1006 {
1007 MOZ_ASSERT(mLocked);
1008 mIndex->Unlock();
1009 mLocked = false;
1010 }
1012 private:
1013 nsRefPtr<CacheIndex> mIndex;
1014 bool mLocked;
1015 };
1017 class CacheIndexAutoUnlock {
1018 public:
1019 CacheIndexAutoUnlock(CacheIndex *aIndex)
1020 : mIndex(aIndex)
1021 , mLocked(false)
1022 {
1023 mIndex->Unlock();
1024 }
1025 ~CacheIndexAutoUnlock()
1026 {
1027 if (!mLocked) {
1028 mIndex->Lock();
1029 }
1030 }
1031 void Lock()
1032 {
1033 MOZ_ASSERT(!mLocked);
1034 mIndex->Lock();
1035 mLocked = true;
1036 }
1037 void Unlock()
1038 {
1039 MOZ_ASSERT(mLocked);
1040 mIndex->Unlock();
1041 mLocked = false;
1042 }
1044 private:
1045 nsRefPtr<CacheIndex> mIndex;
1046 bool mLocked;
1047 };
1049 } // net
1050 } // mozilla
1052 #endif