michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef CacheEntry__h__ michael@0: #define CacheEntry__h__ michael@0: michael@0: #include "nsICacheEntry.h" michael@0: #include "CacheFile.h" michael@0: michael@0: #include "nsIRunnable.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsICacheEntryOpenCallback.h" michael@0: #include "nsICacheEntryDoomCallback.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsRefPtrHashtable.h" michael@0: #include "nsDataHashtable.h" michael@0: #include "nsHashKeys.h" michael@0: #include "nsString.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: michael@0: static inline uint32_t michael@0: PRTimeToSeconds(PRTime t_usec) michael@0: { michael@0: PRTime usec_per_sec = PR_USEC_PER_SEC; michael@0: return uint32_t(t_usec /= usec_per_sec); michael@0: } michael@0: michael@0: #define NowInSeconds() PRTimeToSeconds(PR_Now()) michael@0: michael@0: class nsIStorageStream; michael@0: class nsIOutputStream; michael@0: class nsIURI; michael@0: class nsIThread; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: class CacheStorageService; michael@0: class CacheStorage; michael@0: class CacheFileOutputStream; michael@0: class CacheOutputCloseListener; michael@0: class CacheEntryHandle; michael@0: michael@0: class CacheEntry : public nsICacheEntry michael@0: , public nsIRunnable michael@0: , public CacheFileListener michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSICACHEENTRY michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: CacheEntry(const nsACString& aStorageID, nsIURI* aURI, const nsACString& aEnhanceID, michael@0: bool aUseDisk); michael@0: michael@0: void AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags); michael@0: michael@0: CacheEntryHandle* NewHandle(); michael@0: michael@0: public: michael@0: uint32_t GetMetadataMemoryConsumption(); michael@0: nsCString const &GetStorageID() const { return mStorageID; } michael@0: nsCString const &GetEnhanceID() const { return mEnhanceID; } michael@0: nsIURI* GetURI() const { return mURI; } michael@0: // Accessible only under the CacheStorageService lock (asserts it) michael@0: bool IsUsingDiskLocked() const; michael@0: // Accessible at any time michael@0: bool IsUsingDisk() const { return mUseDisk; } michael@0: bool SetUsingDisk(bool aUsingDisk); michael@0: bool IsReferenced() const; michael@0: bool IsFileDoomed(); michael@0: michael@0: // Methods for entry management (eviction from memory), michael@0: // called only on the management thread. michael@0: michael@0: // TODO make these inline michael@0: double GetFrecency() const; michael@0: uint32_t GetExpirationTime() const; michael@0: michael@0: bool IsRegistered() const; michael@0: bool CanRegister() const; michael@0: void SetRegistered(bool aRegistered); michael@0: michael@0: enum EPurge { michael@0: PURGE_DATA_ONLY_DISK_BACKED, michael@0: PURGE_WHOLE_ONLY_DISK_BACKED, michael@0: PURGE_WHOLE, michael@0: }; michael@0: michael@0: bool Purge(uint32_t aWhat); michael@0: void PurgeAndDoom(); michael@0: void DoomAlreadyRemoved(); michael@0: michael@0: nsresult HashingKeyWithStorage(nsACString &aResult); michael@0: nsresult HashingKey(nsACString &aResult); michael@0: michael@0: static nsresult HashingKey(nsCSubstring const& aStorageID, michael@0: nsCSubstring const& aEnhanceID, michael@0: nsIURI* aURI, michael@0: nsACString &aResult); michael@0: michael@0: static nsresult HashingKey(nsCSubstring const& aStorageID, michael@0: nsCSubstring const& aEnhanceID, michael@0: nsCSubstring const& aURISpec, michael@0: nsACString &aResult); michael@0: michael@0: // Accessed only on the service management thread michael@0: double mFrecency; michael@0: uint32_t mSortingExpirationTime; michael@0: michael@0: // Memory reporting michael@0: size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; michael@0: michael@0: private: michael@0: virtual ~CacheEntry(); michael@0: michael@0: // CacheFileListener michael@0: NS_IMETHOD OnFileReady(nsresult aResult, bool aIsNew); michael@0: NS_IMETHOD OnFileDoomed(nsresult aResult); michael@0: michael@0: // Keep the service alive during life-time of an entry michael@0: nsRefPtr mService; michael@0: michael@0: // We must monitor when a cache entry whose consumer is responsible michael@0: // for writing it the first time gets released. We must then invoke michael@0: // waiting callbacks to not break the chain. michael@0: class Callback michael@0: { michael@0: public: michael@0: Callback(CacheEntry* aEntry, michael@0: nsICacheEntryOpenCallback *aCallback, michael@0: bool aReadOnly, bool aCheckOnAnyThread); michael@0: Callback(Callback const &aThat); michael@0: ~Callback(); michael@0: michael@0: // Called when this callback record changes it's owning entry, michael@0: // mainly during recreation. michael@0: void ExchangeEntry(CacheEntry* aEntry); michael@0: michael@0: // We are raising reference count here to take into account the pending michael@0: // callback (that virtually holds a ref to this entry before it gets michael@0: // it's pointer). michael@0: nsRefPtr mEntry; michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mTargetThread; michael@0: bool mReadOnly : 1; michael@0: bool mCheckOnAnyThread : 1; michael@0: bool mRecheckAfterWrite : 1; michael@0: bool mNotWanted : 1; michael@0: michael@0: nsresult OnCheckThread(bool *aOnCheckThread) const; michael@0: nsresult OnAvailThread(bool *aOnAvailThread) const; michael@0: }; michael@0: michael@0: // Since OnCacheEntryAvailable must be invoked on the main thread michael@0: // we need a runnable for it... michael@0: class AvailableCallbackRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: AvailableCallbackRunnable(CacheEntry* aEntry, michael@0: Callback const &aCallback) michael@0: : mEntry(aEntry) michael@0: , mCallback(aCallback) michael@0: {} michael@0: michael@0: private: michael@0: NS_IMETHOD Run() michael@0: { michael@0: mEntry->InvokeAvailableCallback(mCallback); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr mEntry; michael@0: Callback mCallback; michael@0: }; michael@0: michael@0: // Since OnCacheEntryDoomed must be invoked on the main thread michael@0: // we need a runnable for it... michael@0: class DoomCallbackRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: DoomCallbackRunnable(CacheEntry* aEntry, nsresult aRv) michael@0: : mEntry(aEntry), mRv(aRv) {} michael@0: michael@0: private: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsCOMPtr callback; michael@0: { michael@0: mozilla::MutexAutoLock lock(mEntry->mLock); michael@0: mEntry->mDoomCallback.swap(callback); michael@0: } michael@0: michael@0: if (callback) michael@0: callback->OnCacheEntryDoomed(mRv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr mEntry; michael@0: nsresult mRv; michael@0: }; michael@0: michael@0: // Loads from disk asynchronously michael@0: bool Load(bool aTruncate, bool aPriority); michael@0: void OnLoaded(); michael@0: michael@0: void RememberCallback(Callback const & aCallback); michael@0: void InvokeCallbacksLock(); michael@0: void InvokeCallbacks(); michael@0: bool InvokeCallbacks(bool aReadOnly); michael@0: bool InvokeCallback(Callback & aCallback); michael@0: void InvokeAvailableCallback(Callback const & aCallback); michael@0: michael@0: nsresult OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval); michael@0: michael@0: // When this entry is new and recreated w/o a callback, we need to wrap it michael@0: // with a handle to detect writing consumer is gone. michael@0: CacheEntryHandle* NewWriteHandle(); michael@0: void OnHandleClosed(CacheEntryHandle const* aHandle); michael@0: michael@0: private: michael@0: friend class CacheEntryHandle; michael@0: // Increment/decrements the number of handles keeping this entry. michael@0: void AddHandleRef() { ++mHandlesCount; } michael@0: void ReleaseHandleRef() { --mHandlesCount; } michael@0: // Current number of handles keeping this entry. michael@0: uint32_t HandlesCount() const { return mHandlesCount; } michael@0: michael@0: private: michael@0: friend class CacheOutputCloseListener; michael@0: void OnOutputClosed(); michael@0: michael@0: private: michael@0: // Schedules a background operation on the management thread. michael@0: // When executed on the management thread directly, the operation(s) michael@0: // is (are) executed immediately. michael@0: void BackgroundOp(uint32_t aOperation, bool aForceAsync = false); michael@0: void StoreFrecency(); michael@0: michael@0: // Called only from DoomAlreadyRemoved() michael@0: void DoomFile(); michael@0: michael@0: already_AddRefed ReopenTruncated(bool aMemoryOnly, michael@0: nsICacheEntryOpenCallback* aCallback); michael@0: void TransferCallbacks(CacheEntry & aFromEntry); michael@0: michael@0: mozilla::Mutex mLock; michael@0: michael@0: // Reflects the number of existing handles for this entry michael@0: ::mozilla::ThreadSafeAutoRefCnt mHandlesCount; michael@0: michael@0: nsTArray mCallbacks; michael@0: nsCOMPtr mDoomCallback; michael@0: michael@0: nsRefPtr mFile; michael@0: nsresult mFileStatus; michael@0: nsCOMPtr mURI; michael@0: nsCString mEnhanceID; michael@0: nsCString mStorageID; michael@0: michael@0: // Whether it's allowed to persist the data to disk michael@0: // Synchronized by the service management lock. michael@0: // Hence, leave it as a standalone boolean. michael@0: bool mUseDisk; michael@0: michael@0: // Set when entry is doomed with AsyncDoom() or DoomAlreadyRemoved(). michael@0: // Left as a standalone flag to not bother with locking (there is no need). michael@0: bool mIsDoomed; michael@0: michael@0: // Following flags are all synchronized with the cache entry lock. michael@0: michael@0: // Whether security info has already been looked up in metadata. michael@0: bool mSecurityInfoLoaded : 1; michael@0: // Prevents any callback invocation michael@0: bool mPreventCallbacks : 1; michael@0: // true: after load and an existing file, or after output stream has been opened. michael@0: // note - when opening an input stream, and this flag is false, output stream michael@0: // is open along ; this makes input streams on new entries behave correctly michael@0: // when EOF is reached (WOULD_BLOCK is returned). michael@0: // false: after load and a new file, or dropped to back to false when a writer michael@0: // fails to open an output stream. michael@0: bool mHasData : 1; michael@0: michael@0: #ifdef PR_LOG michael@0: static char const * StateString(uint32_t aState); michael@0: #endif michael@0: michael@0: enum EState { // transiting to: michael@0: NOTLOADED = 0, // -> LOADING | EMPTY michael@0: LOADING = 1, // -> EMPTY | READY michael@0: EMPTY = 2, // -> WRITING michael@0: WRITING = 3, // -> EMPTY | READY michael@0: READY = 4, // -> REVALIDATING michael@0: REVALIDATING = 5 // -> READY michael@0: }; michael@0: michael@0: // State of this entry. michael@0: EState mState; michael@0: michael@0: enum ERegistration { michael@0: NEVERREGISTERED = 0, // The entry has never been registered michael@0: REGISTERED = 1, // The entry is stored in the memory pool index michael@0: DEREGISTERED = 2 // The entry has been removed from the pool michael@0: }; michael@0: michael@0: // Accessed only on the management thread. Records the state of registration michael@0: // this entry in the memory pool intermediate cache. michael@0: ERegistration mRegistration; michael@0: michael@0: // If a new (empty) entry is requested to open an input stream before michael@0: // output stream has been opened, we must open output stream internally michael@0: // on CacheFile and hold until writer releases the entry or opens the output michael@0: // stream for read (then we trade him mOutputStream). michael@0: nsCOMPtr mOutputStream; michael@0: michael@0: // Weak reference to the current writter. There can be more then one michael@0: // writer at a time and OnHandleClosed() must be processed only for the michael@0: // current one. michael@0: CacheEntryHandle* mWriter; michael@0: michael@0: // Background thread scheduled operation. Set (under the lock) one michael@0: // of this flags to tell the background thread what to do. michael@0: class Ops { michael@0: public: michael@0: static uint32_t const REGISTER = 1 << 0; michael@0: static uint32_t const FRECENCYUPDATE = 1 << 1; michael@0: static uint32_t const CALLBACKS = 1 << 2; michael@0: static uint32_t const UNREGISTER = 1 << 3; michael@0: michael@0: Ops() : mFlags(0) { } michael@0: uint32_t Grab() { uint32_t flags = mFlags; mFlags = 0; return flags; } michael@0: bool Set(uint32_t aFlags) { if (mFlags & aFlags) return false; mFlags |= aFlags; return true; } michael@0: private: michael@0: uint32_t mFlags; michael@0: } mBackgroundOperations; michael@0: michael@0: nsCOMPtr mSecurityInfo; michael@0: int64_t mPredictedDataSize; michael@0: mozilla::TimeStamp mLoadStart; michael@0: nsCOMPtr mReleaseThread; michael@0: }; michael@0: michael@0: michael@0: class CacheEntryHandle : public nsICacheEntry michael@0: { michael@0: public: michael@0: CacheEntryHandle(CacheEntry* aEntry); michael@0: virtual ~CacheEntryHandle(); michael@0: CacheEntry* Entry() const { return mEntry; } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_FORWARD_NSICACHEENTRY(mEntry->) michael@0: private: michael@0: nsRefPtr mEntry; michael@0: }; michael@0: michael@0: michael@0: class CacheOutputCloseListener : public nsRunnable michael@0: { michael@0: public: michael@0: void OnOutputClosed(); michael@0: virtual ~CacheOutputCloseListener(); michael@0: michael@0: private: michael@0: friend class CacheEntry; michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: CacheOutputCloseListener(CacheEntry* aEntry); michael@0: michael@0: private: michael@0: nsRefPtr mEntry; michael@0: }; michael@0: michael@0: } // net michael@0: } // mozilla michael@0: michael@0: #endif