mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-03 12:35:58 +00:00
Bug 922741 - make callbacks iteration in CacheEntry smarter, r=michal
This commit is contained in:
parent
4a18021827
commit
2437751f1d
@ -475,8 +475,7 @@ function openCacheEntry(key, cb)
|
||||
},
|
||||
onCacheEntryAvailable: function(entry, isNew, appCache, status) {
|
||||
cb(entry);
|
||||
},
|
||||
get mainThreadOnly() { return true; }
|
||||
}
|
||||
};
|
||||
diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener);
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ namespace net {
|
||||
|
||||
static uint32_t const ENTRY_WANTED =
|
||||
nsICacheEntryOpenCallback::ENTRY_WANTED;
|
||||
static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
|
||||
nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
|
||||
static uint32_t const ENTRY_NEEDS_REVALIDATION =
|
||||
nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
|
||||
static uint32_t const ENTRY_NOT_WANTED =
|
||||
@ -54,6 +56,53 @@ CacheEntry::Handle::~Handle()
|
||||
MOZ_COUNT_DTOR(CacheEntry::Handle);
|
||||
}
|
||||
|
||||
// CacheEntry::Callback
|
||||
|
||||
CacheEntry::Callback::Callback(nsICacheEntryOpenCallback *aCallback,
|
||||
bool aReadOnly, bool aCheckOnAnyThread)
|
||||
: mCallback(aCallback)
|
||||
, mTargetThread(do_GetCurrentThread())
|
||||
, mReadOnly(aReadOnly)
|
||||
, mCheckOnAnyThread(aCheckOnAnyThread)
|
||||
, mRecheckAfterWrite(false)
|
||||
, mNotWanted(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(CacheEntry::Callback);
|
||||
}
|
||||
|
||||
CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
|
||||
: mCallback(aThat.mCallback)
|
||||
, mTargetThread(aThat.mTargetThread)
|
||||
, mReadOnly(aThat.mReadOnly)
|
||||
, mCheckOnAnyThread(aThat.mCheckOnAnyThread)
|
||||
, mRecheckAfterWrite(aThat.mRecheckAfterWrite)
|
||||
, mNotWanted(aThat.mNotWanted)
|
||||
{
|
||||
MOZ_COUNT_CTOR(CacheEntry::Callback);
|
||||
}
|
||||
|
||||
CacheEntry::Callback::~Callback()
|
||||
{
|
||||
MOZ_COUNT_DTOR(CacheEntry::Callback);
|
||||
}
|
||||
|
||||
nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
|
||||
{
|
||||
if (!mCheckOnAnyThread) {
|
||||
// Check we are on the target
|
||||
return mTargetThread->IsOnCurrentThread(aOnCheckThread);
|
||||
}
|
||||
|
||||
// We can invoke check anywhere
|
||||
*aOnCheckThread = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
|
||||
{
|
||||
return mTargetThread->IsOnCurrentThread(aOnAvailThread);
|
||||
}
|
||||
|
||||
// CacheEntry
|
||||
|
||||
NS_IMPL_ISUPPORTS3(CacheEntry,
|
||||
@ -76,13 +125,13 @@ CacheEntry::CacheEntry(const nsACString& aStorageID,
|
||||
, mIsDoomed(false)
|
||||
, mSecurityInfoLoaded(false)
|
||||
, mPreventCallbacks(false)
|
||||
, mHasMainThreadOnlyCallback(false)
|
||||
, mHasData(false)
|
||||
, mState(NOTLOADED)
|
||||
, mRegistration(NEVERREGISTERED)
|
||||
, mWriter(nullptr)
|
||||
, mPredictedDataSize(0)
|
||||
, mDataSize(0)
|
||||
, mReleaseThread(NS_GetCurrentThread())
|
||||
{
|
||||
MOZ_COUNT_CTOR(CacheEntry);
|
||||
|
||||
@ -94,7 +143,7 @@ CacheEntry::CacheEntry(const nsACString& aStorageID,
|
||||
|
||||
CacheEntry::~CacheEntry()
|
||||
{
|
||||
ProxyReleaseMainThread(mURI);
|
||||
ProxyRelease(mURI, mReleaseThread);
|
||||
|
||||
LOG(("CacheEntry::~CacheEntry [this=%p]", this));
|
||||
MOZ_COUNT_DTOR(CacheEntry);
|
||||
@ -166,26 +215,20 @@ void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags
|
||||
bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
|
||||
bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
|
||||
bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
|
||||
|
||||
bool mainThreadOnly;
|
||||
if (aCallback && NS_FAILED(aCallback->GetMainThreadOnly(&mainThreadOnly)))
|
||||
mainThreadOnly = true; // rather play safe...
|
||||
bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
|
||||
|
||||
MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
|
||||
MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
|
||||
|
||||
Callback callback(aCallback, readonly, multithread);
|
||||
|
||||
mozilla::MutexAutoLock lock(mLock);
|
||||
|
||||
if (Load(truncate, priority) ||
|
||||
PendingCallbacks() ||
|
||||
!InvokeCallback(aCallback, readonly)) {
|
||||
!InvokeCallback(callback)) {
|
||||
// Load in progress or callback bypassed...
|
||||
if (mainThreadOnly) {
|
||||
LOG((" callback is main-thread only"));
|
||||
mHasMainThreadOnlyCallback = true;
|
||||
}
|
||||
|
||||
RememberCallback(aCallback, readonly);
|
||||
RememberCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,52 +381,46 @@ already_AddRefed<CacheEntry> CacheEntry::ReopenTruncated(nsICacheEntryOpenCallba
|
||||
|
||||
newEntry->TransferCallbacks(*this);
|
||||
mCallbacks.Clear();
|
||||
mReadOnlyCallbacks.Clear();
|
||||
mHasMainThreadOnlyCallback = false;
|
||||
|
||||
return newEntry.forget();
|
||||
}
|
||||
|
||||
void CacheEntry::TransferCallbacks(CacheEntry const& aFromEntry)
|
||||
void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry)
|
||||
{
|
||||
mozilla::MutexAutoLock lock(mLock);
|
||||
|
||||
LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
|
||||
this, &aFromEntry));
|
||||
|
||||
mCallbacks.AppendObjects(aFromEntry.mCallbacks);
|
||||
mReadOnlyCallbacks.AppendObjects(aFromEntry.mReadOnlyCallbacks);
|
||||
if (aFromEntry.mHasMainThreadOnlyCallback)
|
||||
mHasMainThreadOnlyCallback = true;
|
||||
if (!mCallbacks.Length())
|
||||
mCallbacks.SwapElements(aFromEntry.mCallbacks);
|
||||
else
|
||||
mCallbacks.AppendElements(aFromEntry.mCallbacks);
|
||||
|
||||
if (mCallbacks.Length() || mReadOnlyCallbacks.Length())
|
||||
if (mCallbacks.Length())
|
||||
BackgroundOp(Ops::CALLBACKS, true);
|
||||
}
|
||||
|
||||
void CacheEntry::RememberCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
bool aReadOnly)
|
||||
void CacheEntry::RememberCallback(Callback const& aCallback)
|
||||
{
|
||||
// AsyncOpen can be called w/o a callback reference (when this is a new/truncated entry)
|
||||
if (!aCallback)
|
||||
if (!aCallback.mCallback)
|
||||
return;
|
||||
|
||||
LOG(("CacheEntry::RememberCallback [this=%p, cb=%p]", this, aCallback));
|
||||
LOG(("CacheEntry::RememberCallback [this=%p, cb=%p]", this, aCallback.mCallback.get()));
|
||||
|
||||
mLock.AssertCurrentThreadOwns();
|
||||
|
||||
if (!aReadOnly)
|
||||
mCallbacks.AppendObject(aCallback);
|
||||
else
|
||||
mReadOnlyCallbacks.AppendObject(aCallback);
|
||||
mCallbacks.AppendElement(aCallback);
|
||||
}
|
||||
|
||||
bool CacheEntry::PendingCallbacks()
|
||||
{
|
||||
mLock.AssertCurrentThreadOwns();
|
||||
return mCallbacks.Length() || mReadOnlyCallbacks.Length();
|
||||
return mCallbacks.Length();
|
||||
}
|
||||
|
||||
void CacheEntry::InvokeCallbacksMainThread()
|
||||
void CacheEntry::InvokeCallbacksLock()
|
||||
{
|
||||
mozilla::MutexAutoLock lock(mLock);
|
||||
InvokeCallbacks();
|
||||
@ -391,80 +428,84 @@ void CacheEntry::InvokeCallbacksMainThread()
|
||||
|
||||
void CacheEntry::InvokeCallbacks()
|
||||
{
|
||||
LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
|
||||
|
||||
mLock.AssertCurrentThreadOwns();
|
||||
|
||||
do {
|
||||
if (mPreventCallbacks) {
|
||||
LOG(("CacheEntry::InvokeCallbacks END [this=%p] callbacks prevented!", this));
|
||||
return;
|
||||
}
|
||||
LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
|
||||
|
||||
if (!mCallbacks.Count()) {
|
||||
LOG((" no r/w callbacks"));
|
||||
break;
|
||||
}
|
||||
|
||||
if (mHasMainThreadOnlyCallback && !NS_IsMainThread()) {
|
||||
nsRefPtr<nsRunnableMethod<CacheEntry> > event =
|
||||
NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksMainThread);
|
||||
NS_DispatchToMainThread(event);
|
||||
LOG(("CacheEntry::InvokeCallbacks END [this=%p] dispatching to maintread", this));
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> callback = mCallbacks[0];
|
||||
mCallbacks.RemoveElementAt(0);
|
||||
|
||||
if (!InvokeCallback(callback, false)) {
|
||||
mCallbacks.InsertElementAt(0, callback);
|
||||
LOG(("CacheEntry::InvokeCallbacks END [this=%p] callback bypassed", this));
|
||||
return;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
while (mReadOnlyCallbacks.Count()) {
|
||||
if (mHasMainThreadOnlyCallback && !NS_IsMainThread()) {
|
||||
nsRefPtr<nsRunnableMethod<CacheEntry> > event =
|
||||
NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksMainThread);
|
||||
NS_DispatchToMainThread(event);
|
||||
LOG(("CacheEntry::InvokeCallbacks END [this=%p] dispatching to maintread", this));
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> callback = mReadOnlyCallbacks[0];
|
||||
mReadOnlyCallbacks.RemoveElementAt(0);
|
||||
|
||||
if (!InvokeCallback(callback, true)) {
|
||||
// Didn't trigger, so we must stop
|
||||
mReadOnlyCallbacks.InsertElementAt(0, callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mCallbacks.Count() && !mReadOnlyCallbacks.Count())
|
||||
mHasMainThreadOnlyCallback = false;
|
||||
// Invoke first all r/w callbacks, then all r/o callbacks.
|
||||
if (InvokeCallbacks(false))
|
||||
InvokeCallbacks(true);
|
||||
|
||||
LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
|
||||
}
|
||||
|
||||
bool CacheEntry::InvokeCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
bool aReadOnly)
|
||||
bool CacheEntry::InvokeCallbacks(bool aReadOnly)
|
||||
{
|
||||
mLock.AssertCurrentThreadOwns();
|
||||
|
||||
uint32_t i = 0;
|
||||
while (i < mCallbacks.Length()) {
|
||||
if (mPreventCallbacks) {
|
||||
LOG((" callbacks prevented!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
|
||||
LOG((" entry is being written/revalidated"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mCallbacks[i].mReadOnly != aReadOnly) {
|
||||
// Callback is not r/w or r/o, go to another one in line
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool onCheckThread;
|
||||
nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && !onCheckThread) {
|
||||
// Redispatch to the target thread
|
||||
nsRefPtr<nsRunnableMethod<CacheEntry> > event =
|
||||
NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksLock);
|
||||
|
||||
rv = mCallbacks[i].mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
LOG((" re-dispatching to target thread"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Callback callback = mCallbacks[i];
|
||||
mCallbacks.RemoveElementAt(i);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
|
||||
// Callback didn't fire, put it back and go to another one in line.
|
||||
// Only reason InvokeCallback returns false is that onCacheEntryCheck
|
||||
// returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
|
||||
// readers or potential writers would be unnecessarily kept from being
|
||||
// invoked.
|
||||
mCallbacks.InsertElementAt(i, callback);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CacheEntry::InvokeCallback(Callback & aCallback)
|
||||
{
|
||||
LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
|
||||
this, StateString(mState), aCallback));
|
||||
this, StateString(mState), aCallback.mCallback.get()));
|
||||
|
||||
mLock.AssertCurrentThreadOwns();
|
||||
|
||||
bool notWanted = false;
|
||||
|
||||
// When this entry is doomed we want to notify the callback any time
|
||||
if (!mIsDoomed) {
|
||||
// When we are here, the entry must be loaded from disk
|
||||
MOZ_ASSERT(mState > LOADING);
|
||||
|
||||
if (mState == WRITING ||
|
||||
mState == REVALIDATING) {
|
||||
if (mState == WRITING || mState == REVALIDATING) {
|
||||
// Prevent invoking other callbacks since one of them is now writing
|
||||
// or revalidating this entry. No consumers should get this entry
|
||||
// until metadata are filled with values downloaded from the server
|
||||
@ -473,7 +514,10 @@ bool CacheEntry::InvokeCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aReadOnly) {
|
||||
// mRecheckAfterWrite flag already set means the callback has already passed
|
||||
// the onCacheEntryCheck call. Until the current write is not finished this
|
||||
// callback will be bypassed.
|
||||
if (!aCallback.mReadOnly && !aCallback.mRecheckAfterWrite) {
|
||||
if (mState == EMPTY) {
|
||||
// Advance to writing state, we expect to invoke the callback and let
|
||||
// it fill content of this entry. Must set and check the state here
|
||||
@ -482,7 +526,7 @@ bool CacheEntry::InvokeCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
LOG((" advancing to WRITING state"));
|
||||
}
|
||||
|
||||
if (!aCallback) {
|
||||
if (!aCallback.mCallback) {
|
||||
// We can be given no callback only in case of recreate, it is ok
|
||||
// to advance to WRITING state since the caller of recreate is expected
|
||||
// to write this entry now.
|
||||
@ -496,7 +540,8 @@ bool CacheEntry::InvokeCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
// mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
|
||||
mozilla::MutexAutoUnlock unlock(mLock);
|
||||
|
||||
nsresult rv = aCallback->OnCacheEntryCheck(this, nullptr, &checkResult);
|
||||
nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
|
||||
this, nullptr, &checkResult);
|
||||
LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult));
|
||||
|
||||
if (NS_FAILED(rv))
|
||||
@ -510,6 +555,12 @@ bool CacheEntry::InvokeCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
// Proceed to callback...
|
||||
break;
|
||||
|
||||
case RECHECK_AFTER_WRITE_FINISHED:
|
||||
LOG((" consumer will check on the entry again after write is done"));
|
||||
// The consumer wants the entry to complete first.
|
||||
aCallback.mRecheckAfterWrite = true;
|
||||
break;
|
||||
|
||||
case ENTRY_NEEDS_REVALIDATION:
|
||||
LOG((" will be holding callbacks until entry is revalidated"));
|
||||
// State is READY now and from that state entry cannot transit to any other
|
||||
@ -521,46 +572,73 @@ bool CacheEntry::InvokeCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
case ENTRY_NOT_WANTED:
|
||||
LOG((" consumer not interested in the entry"));
|
||||
// Do not give this entry to the consumer, it is not interested in us.
|
||||
notWanted = true;
|
||||
aCallback.mNotWanted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aCallback) {
|
||||
if (aCallback.mCallback) {
|
||||
if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
|
||||
// If we don't have data and the callback wants a complete entry,
|
||||
// don't invoke now.
|
||||
bool bypass = !mHasData;
|
||||
if (!bypass) {
|
||||
int64_t _unused;
|
||||
bypass = !mFile->DataSize(&_unused);
|
||||
}
|
||||
|
||||
if (bypass) {
|
||||
LOG((" bypassing, entry data still being written"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Entry is complete now, do the check+avail call again
|
||||
aCallback.mRecheckAfterWrite = false;
|
||||
return InvokeCallback(aCallback);
|
||||
}
|
||||
|
||||
mozilla::MutexAutoUnlock unlock(mLock);
|
||||
InvokeAvailableCallback(aCallback, aReadOnly, notWanted);
|
||||
InvokeAvailableCallback(aCallback);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CacheEntry::InvokeAvailableCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
bool aReadOnly,
|
||||
bool aNotWanted)
|
||||
void CacheEntry::InvokeAvailableCallback(Callback const & aCallback)
|
||||
{
|
||||
LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
|
||||
this, StateString(mState), aCallback, aReadOnly, aNotWanted));
|
||||
this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted));
|
||||
|
||||
nsresult rv;
|
||||
|
||||
uint32_t const state = mState;
|
||||
|
||||
// When we are here, the entry must be loaded from disk
|
||||
MOZ_ASSERT(state > LOADING || mIsDoomed);
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
// Must happen on the main thread :(
|
||||
nsRefPtr<AvailableCallbackRunnable> event =
|
||||
new AvailableCallbackRunnable(this, aCallback, aReadOnly, aNotWanted);
|
||||
NS_DispatchToMainThread(event);
|
||||
bool onAvailThread;
|
||||
rv = aCallback.OnAvailThread(&onAvailThread);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG((" target thread dead?"));
|
||||
return;
|
||||
}
|
||||
|
||||
// This happens only on the main thread / :( /
|
||||
if (!onAvailThread) {
|
||||
// Dispatch to the right thread
|
||||
nsRefPtr<AvailableCallbackRunnable> event =
|
||||
new AvailableCallbackRunnable(this, aCallback);
|
||||
|
||||
if (mIsDoomed || aNotWanted) {
|
||||
rv = aCallback.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
|
||||
LOG((" redispatched, (rv = 0x%08x)", rv));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsDoomed || aCallback.mNotWanted) {
|
||||
LOG((" doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
|
||||
aCallback->OnCacheEntryAvailable(nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
|
||||
aCallback.mCallback->OnCacheEntryAvailable(
|
||||
nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -571,13 +649,15 @@ void CacheEntry::InvokeAvailableCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
BackgroundOp(Ops::FRECENCYUPDATE);
|
||||
}
|
||||
|
||||
aCallback->OnCacheEntryAvailable(this, false, nullptr, NS_OK);
|
||||
aCallback.mCallback->OnCacheEntryAvailable(
|
||||
this, false, nullptr, NS_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aReadOnly) {
|
||||
if (aCallback.mReadOnly) {
|
||||
LOG((" r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
|
||||
aCallback->OnCacheEntryAvailable(nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
|
||||
aCallback.mCallback->OnCacheEntryAvailable(
|
||||
nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -589,7 +669,8 @@ void CacheEntry::InvokeAvailableCallback(nsICacheEntryOpenCallback* aCallback,
|
||||
// Consumer will be responsible to fill or validate the entry metadata and data.
|
||||
|
||||
nsRefPtr<Handle> handle = NewWriteHandle();
|
||||
nsresult rv = aCallback->OnCacheEntryAvailable(handle, state == WRITING, nullptr, NS_OK);
|
||||
rv = aCallback.mCallback->OnCacheEntryAvailable(
|
||||
handle, state == WRITING, nullptr, NS_OK);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG((" writing/revalidating failed (0x%08x)", rv));
|
||||
@ -652,6 +733,15 @@ void CacheEntry::OnWriterClosed(Handle const* aHandle)
|
||||
}
|
||||
}
|
||||
|
||||
void CacheEntry::OnOutputClosed()
|
||||
{
|
||||
// Called when the file's output stream is closed. Invoke any callbacks
|
||||
// waiting for complete entry.
|
||||
|
||||
mozilla::MutexAutoLock lock(mLock);
|
||||
InvokeCallbacks();
|
||||
}
|
||||
|
||||
bool CacheEntry::UsingDisk() const
|
||||
{
|
||||
CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
|
||||
@ -841,9 +931,6 @@ nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream *
|
||||
|
||||
MOZ_ASSERT(mState > LOADING);
|
||||
|
||||
if (!mFile)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
nsresult rv;
|
||||
|
||||
// No need to sync on mUseDisk here, we don't need to be consistent
|
||||
@ -853,8 +940,11 @@ nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream *
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
nsRefPtr<CacheOutputCloseListener> listener =
|
||||
new CacheOutputCloseListener(this);
|
||||
|
||||
nsCOMPtr<nsIOutputStream> stream;
|
||||
rv = mFile->OpenOutputStream(getter_AddRefs(stream));
|
||||
rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsISeekableStream> seekable =
|
||||
@ -924,17 +1014,11 @@ NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
|
||||
|
||||
NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
|
||||
|
||||
nsRefPtr<CacheFile> file;
|
||||
{
|
||||
mozilla::MutexAutoLock lock(mLock);
|
||||
|
||||
mSecurityInfo = aSecurityInfo;
|
||||
mSecurityInfoLoaded = true;
|
||||
|
||||
if (!mFile)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
file = mFile;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISerializable> serializable =
|
||||
@ -1271,7 +1355,7 @@ void CacheEntry::DoomAlreadyRemoved()
|
||||
{
|
||||
mozilla::MutexAutoLock lock(mLock);
|
||||
|
||||
if (mCallbacks.Length() || mReadOnlyCallbacks.Length()) {
|
||||
if (mCallbacks.Length()) {
|
||||
// Must force post here since may be indirectly called from
|
||||
// InvokeCallbacks of this entry and we don't want reentrancy here.
|
||||
BackgroundOp(Ops::CALLBACKS, true);
|
||||
@ -1348,5 +1432,32 @@ void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
|
||||
}
|
||||
}
|
||||
|
||||
// CacheOutputCloseListener
|
||||
|
||||
CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
|
||||
: mEntry(aEntry)
|
||||
{
|
||||
MOZ_COUNT_CTOR(CacheOutputCloseListener);
|
||||
}
|
||||
|
||||
CacheOutputCloseListener::~CacheOutputCloseListener()
|
||||
{
|
||||
MOZ_COUNT_DTOR(CacheOutputCloseListener);
|
||||
}
|
||||
|
||||
void CacheOutputCloseListener::OnOutputClosed()
|
||||
{
|
||||
// We need this class and to redispatch since this callback is invoked
|
||||
// under the file's lock and to do the job we need to enter the entry's
|
||||
// lock too. That would lead to potential deadlocks.
|
||||
NS_DispatchToCurrentThread(this);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP CacheOutputCloseListener::Run()
|
||||
{
|
||||
mEntry->OnOutputClosed();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // net
|
||||
} // mozilla
|
||||
|
@ -35,19 +35,15 @@ PRTimeToSeconds(PRTime t_usec)
|
||||
class nsIStorageStream;
|
||||
class nsIOutputStream;
|
||||
class nsIURI;
|
||||
class nsIThread;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class CacheStorageService;
|
||||
class CacheStorage;
|
||||
|
||||
namespace {
|
||||
class FrecencyComparator;
|
||||
class ExpirationComparator;
|
||||
class EvictionRunnable;
|
||||
class WalkRunnable;
|
||||
}
|
||||
class CacheFileOutputStream;
|
||||
class CacheOutputCloseListener;
|
||||
|
||||
class CacheEntry : public nsICacheEntry
|
||||
, public nsIRunnable
|
||||
@ -129,29 +125,45 @@ private:
|
||||
nsRefPtr<CacheEntry> mEntry;
|
||||
};
|
||||
|
||||
class Callback
|
||||
{
|
||||
public:
|
||||
Callback(nsICacheEntryOpenCallback *aCallback,
|
||||
bool aReadOnly, bool aCheckOnAnyThread);
|
||||
Callback(Callback const &aThat);
|
||||
~Callback();
|
||||
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
|
||||
nsCOMPtr<nsIThread> mTargetThread;
|
||||
bool mReadOnly : 1;
|
||||
bool mCheckOnAnyThread : 1;
|
||||
bool mRecheckAfterWrite : 1;
|
||||
bool mNotWanted : 1;
|
||||
|
||||
nsresult OnCheckThread(bool *aOnCheckThread) const;
|
||||
nsresult OnAvailThread(bool *aOnAvailThread) const;
|
||||
};
|
||||
|
||||
// Since OnCacheEntryAvailable must be invoked on the main thread
|
||||
// we need a runnable for it...
|
||||
class AvailableCallbackRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
AvailableCallbackRunnable(CacheEntry* aEntry,
|
||||
nsICacheEntryOpenCallback* aCallback,
|
||||
bool aReadOnly,
|
||||
bool aNotWanted)
|
||||
: mEntry(aEntry), mCallback(aCallback)
|
||||
, mReadOnly(aReadOnly), mNotWanted(aNotWanted) {}
|
||||
Callback const &aCallback)
|
||||
: mEntry(aEntry)
|
||||
, mCallback(aCallback)
|
||||
{}
|
||||
|
||||
private:
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
mEntry->InvokeAvailableCallback(mCallback, mReadOnly, mNotWanted);
|
||||
mEntry->InvokeAvailableCallback(mCallback);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<CacheEntry> mEntry;
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
|
||||
bool mReadOnly : 1;
|
||||
bool mNotWanted : 1;
|
||||
Callback mCallback;
|
||||
};
|
||||
|
||||
// Since OnCacheEntryDoomed must be invoked on the main thread
|
||||
@ -184,12 +196,13 @@ private:
|
||||
bool Load(bool aTruncate, bool aPriority);
|
||||
void OnLoaded();
|
||||
|
||||
void RememberCallback(nsICacheEntryOpenCallback* aCallback, bool aReadOnly);
|
||||
void RememberCallback(Callback const & aCallback);
|
||||
bool PendingCallbacks();
|
||||
void InvokeCallbacksLock();
|
||||
void InvokeCallbacks();
|
||||
bool InvokeCallback(nsICacheEntryOpenCallback* aCallback, bool aReadOnly);
|
||||
void InvokeAvailableCallback(nsICacheEntryOpenCallback* aCallback, bool aReadOnly, bool aNotWanted);
|
||||
void InvokeCallbacksMainThread();
|
||||
bool InvokeCallbacks(bool aReadOnly);
|
||||
bool InvokeCallback(Callback & aCallback);
|
||||
void InvokeAvailableCallback(Callback const & aCallback);
|
||||
|
||||
nsresult OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval);
|
||||
|
||||
@ -198,17 +211,21 @@ private:
|
||||
Handle* NewWriteHandle();
|
||||
void OnWriterClosed(Handle const* aHandle);
|
||||
|
||||
private:
|
||||
friend class CacheOutputCloseListener;
|
||||
void OnOutputClosed();
|
||||
|
||||
// Schedules a background operation on the management thread.
|
||||
// When executed on the management thread directly, the operation(s)
|
||||
// is (are) executed immediately.
|
||||
void BackgroundOp(uint32_t aOperation, bool aForceAsync = false);
|
||||
|
||||
already_AddRefed<CacheEntry> ReopenTruncated(nsICacheEntryOpenCallback* aCallback);
|
||||
void TransferCallbacks(CacheEntry const& aFromEntry);
|
||||
void TransferCallbacks(CacheEntry & aFromEntry);
|
||||
|
||||
mozilla::Mutex mLock;
|
||||
|
||||
nsCOMArray<nsICacheEntryOpenCallback> mCallbacks, mReadOnlyCallbacks;
|
||||
nsTArray<Callback> mCallbacks;
|
||||
nsCOMPtr<nsICacheEntryDoomCallback> mDoomCallback;
|
||||
|
||||
nsRefPtr<CacheFile> mFile;
|
||||
@ -232,8 +249,6 @@ private:
|
||||
bool mSecurityInfoLoaded : 1;
|
||||
// Prevents any callback invocation
|
||||
bool mPreventCallbacks : 1;
|
||||
// Way around when having a callback that cannot be invoked on non-main thread
|
||||
bool mHasMainThreadOnlyCallback : 1;
|
||||
// true: after load and an existing file, or after output stream has been opened.
|
||||
// note - when opening an input stream, and this flag is false, output stream
|
||||
// is open along ; this makes input streams on new entries behave correctly
|
||||
@ -301,6 +316,24 @@ private:
|
||||
uint32_t mDataSize; // ???
|
||||
|
||||
mozilla::TimeStamp mLoadStart;
|
||||
|
||||
nsCOMPtr<nsIThread> mReleaseThread;
|
||||
};
|
||||
|
||||
class CacheOutputCloseListener : public nsRunnable
|
||||
{
|
||||
public:
|
||||
void OnOutputClosed();
|
||||
virtual ~CacheOutputCloseListener();
|
||||
|
||||
private:
|
||||
friend class CacheEntry;
|
||||
|
||||
NS_DECL_NSIRUNNABLE
|
||||
CacheOutputCloseListener(CacheEntry* aEntry);
|
||||
|
||||
private:
|
||||
nsRefPtr<CacheEntry> mEntry;
|
||||
};
|
||||
|
||||
} // net
|
||||
|
@ -807,7 +807,7 @@ CacheFile::OpenInputStream(nsIInputStream **_retval)
|
||||
}
|
||||
|
||||
nsresult
|
||||
CacheFile::OpenOutputStream(nsIOutputStream **_retval)
|
||||
CacheFile::OpenOutputStream(CacheOutputCloseListener *aCloseListener, nsIOutputStream **_retval)
|
||||
{
|
||||
CacheFileAutoLock lock(this);
|
||||
|
||||
@ -827,7 +827,7 @@ CacheFile::OpenOutputStream(nsIOutputStream **_retval)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
mOutput = new CacheFileOutputStream(this);
|
||||
mOutput = new CacheFileOutputStream(this, aCloseListener);
|
||||
|
||||
LOG(("CacheFile::OpenOutputStream() - Creating new output stream %p "
|
||||
"[this=%p]", mOutput, this));
|
||||
@ -1338,6 +1338,9 @@ CacheFile::RemoveOutput(CacheFileOutputStream *aOutput)
|
||||
if (!mMemoryOnly)
|
||||
WriteMetadataIfNeeded();
|
||||
|
||||
// Notify close listener as the last action
|
||||
aOutput->NotifyCloseListener();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ namespace net {
|
||||
|
||||
class CacheFileInputStream;
|
||||
class CacheFileOutputStream;
|
||||
class CacheOutputCloseListener;
|
||||
class MetadataWriteTimer;
|
||||
|
||||
#define CACHEFILELISTENER_IID \
|
||||
@ -77,7 +78,7 @@ public:
|
||||
NS_IMETHOD OnMetadataWritten(nsresult aResult);
|
||||
|
||||
NS_IMETHOD OpenInputStream(nsIInputStream **_retval);
|
||||
NS_IMETHOD OpenOutputStream(nsIOutputStream **_retval);
|
||||
NS_IMETHOD OpenOutputStream(CacheOutputCloseListener *aCloseListener, nsIOutputStream **_retval);
|
||||
NS_IMETHOD SetMemoryOnly();
|
||||
NS_IMETHOD Doom(CacheFileListener *aCallback);
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "CacheFileOutputStream.h"
|
||||
|
||||
#include "CacheFile.h"
|
||||
#include "CacheEntry.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
@ -43,8 +44,10 @@ NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
|
||||
NS_INTERFACE_MAP_END_THREADSAFE
|
||||
|
||||
CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile)
|
||||
CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile,
|
||||
CacheOutputCloseListener *aCloseListener)
|
||||
: mFile(aFile)
|
||||
, mCloseListener(aCloseListener)
|
||||
, mPos(0)
|
||||
, mClosed(false)
|
||||
, mStatus(NS_OK)
|
||||
@ -294,6 +297,16 @@ CacheFileOutputStream::OnChunkUpdated(CacheFileChunk *aChunk)
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
void CacheFileOutputStream::NotifyCloseListener()
|
||||
{
|
||||
nsRefPtr<CacheOutputCloseListener> listener;
|
||||
listener.swap(mCloseListener);
|
||||
if (!listener)
|
||||
return;
|
||||
|
||||
listener->OnOutputClosed();
|
||||
}
|
||||
|
||||
void
|
||||
CacheFileOutputStream::ReleaseChunk()
|
||||
{
|
||||
|
@ -16,6 +16,7 @@ namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class CacheFile;
|
||||
class CacheOutputCloseListener;
|
||||
|
||||
class CacheFileOutputStream : public nsIAsyncOutputStream
|
||||
, public nsISeekableStream
|
||||
@ -27,7 +28,7 @@ class CacheFileOutputStream : public nsIAsyncOutputStream
|
||||
NS_DECL_NSISEEKABLESTREAM
|
||||
|
||||
public:
|
||||
CacheFileOutputStream(CacheFile *aFile);
|
||||
CacheFileOutputStream(CacheFile *aFile, CacheOutputCloseListener *aCloseListener);
|
||||
|
||||
NS_IMETHOD OnChunkRead(nsresult aResult, CacheFileChunk *aChunk);
|
||||
NS_IMETHOD OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk);
|
||||
@ -35,6 +36,8 @@ public:
|
||||
CacheFileChunk *aChunk);
|
||||
NS_IMETHOD OnChunkUpdated(CacheFileChunk *aChunk);
|
||||
|
||||
void NotifyCloseListener();
|
||||
|
||||
private:
|
||||
virtual ~CacheFileOutputStream();
|
||||
|
||||
@ -45,6 +48,7 @@ private:
|
||||
|
||||
nsRefPtr<CacheFile> mFile;
|
||||
nsRefPtr<CacheFileChunk> mChunk;
|
||||
nsRefPtr<CacheOutputCloseListener> mCloseListener;
|
||||
int64_t mPos;
|
||||
bool mClosed;
|
||||
nsresult mStatus;
|
||||
|
@ -199,14 +199,19 @@ private:
|
||||
};
|
||||
|
||||
template<class T>
|
||||
void ProxyReleaseMainThread(nsCOMPtr<T> &object)
|
||||
void ProxyRelease(nsCOMPtr<T> &object, nsIThread* thread)
|
||||
{
|
||||
T* release;
|
||||
object.forget(&release);
|
||||
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
NS_ProxyRelease(mainThread, release);
|
||||
NS_ProxyRelease(thread, release);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void ProxyReleaseMainThread(nsCOMPtr<T> &object)
|
||||
{
|
||||
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
||||
ProxyRelease(object, mainThread);
|
||||
}
|
||||
|
||||
} // net
|
||||
|
@ -25,6 +25,8 @@
|
||||
static NS_DEFINE_CID(kStreamTransportServiceCID,
|
||||
NS_STREAMTRANSPORTSERVICE_CID);
|
||||
|
||||
static uint32_t const CHECK_MULTITHREADED = nsICacheStorage::CHECK_MULTITHREADED;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
@ -511,8 +513,9 @@ _OldCacheLoad::_OldCacheLoad(nsCSubstring const& aScheme,
|
||||
, mLoadInfo(GetLoadContextInfo(aLoadInfo))
|
||||
, mFlags(aFlags)
|
||||
, mWriteToDisk(aWriteToDisk)
|
||||
, mMainThreadOnly(true)
|
||||
, mNew(true)
|
||||
, mOpening(true)
|
||||
, mSync(false)
|
||||
, mStatus(NS_ERROR_UNEXPECTED)
|
||||
, mRunCount(0)
|
||||
, mAppCache(aAppCache)
|
||||
@ -529,26 +532,22 @@ _OldCacheLoad::~_OldCacheLoad()
|
||||
nsresult _OldCacheLoad::Start()
|
||||
{
|
||||
LOG(("_OldCacheLoad::Start [this=%p, key=%s]", this, mCacheKey.get()));
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mLoadStart = mozilla::TimeStamp::Now();
|
||||
|
||||
bool mainThreadOnly;
|
||||
if (mCallback && (
|
||||
NS_SUCCEEDED(mCallback->GetMainThreadOnly(&mainThreadOnly)) &&
|
||||
!mainThreadOnly)) {
|
||||
mMainThreadOnly = false;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
|
||||
// Consumers that can invoke this code as first and off the main thread
|
||||
// are responsible for initiating these two services on the main thread.
|
||||
// Currently this is only nsWyciwygChannel.
|
||||
|
||||
// XXX: Start the cache service; otherwise DispatchToCacheIOThread will
|
||||
// fail.
|
||||
nsCOMPtr<nsICacheService> service =
|
||||
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
|
||||
|
||||
// Ensure the stream transport service gets initialized on the main thread
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if (NS_SUCCEEDED(rv) && NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIStreamTransportService> sts =
|
||||
do_GetService(kStreamTransportServiceCID, &rv);
|
||||
}
|
||||
@ -558,7 +557,20 @@ nsresult _OldCacheLoad::Start()
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = mCacheThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
bool onCacheTarget;
|
||||
rv = mCacheThread->IsOnCurrentThread(&onCacheTarget);
|
||||
if (NS_SUCCEEDED(rv) && onCacheTarget) {
|
||||
mSync = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if (mSync) {
|
||||
rv = Run();
|
||||
}
|
||||
else {
|
||||
rv = mCacheThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
@ -571,7 +583,8 @@ _OldCacheLoad::Run()
|
||||
|
||||
nsresult rv;
|
||||
|
||||
if (!NS_IsMainThread()) {
|
||||
if (mOpening) {
|
||||
mOpening = false;
|
||||
nsCOMPtr<nsICacheSession> session;
|
||||
rv = GetCacheSession(mScheme, mWriteToDisk, mLoadInfo, mAppCache,
|
||||
getter_AddRefs(session));
|
||||
@ -590,8 +603,21 @@ _OldCacheLoad::Run()
|
||||
LOG((" session->AsyncOpenCacheEntry with access=%d", cacheAccess));
|
||||
|
||||
bool bypassBusy = mFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
|
||||
rv = session->AsyncOpenCacheEntry(mCacheKey, cacheAccess, this, bypassBusy);
|
||||
|
||||
if (mSync && cacheAccess == nsICache::ACCESS_WRITE) {
|
||||
nsCOMPtr<nsICacheEntryDescriptor> entry;
|
||||
rv = session->OpenCacheEntry(mCacheKey, cacheAccess, bypassBusy,
|
||||
getter_AddRefs(entry));
|
||||
|
||||
nsCacheAccessMode grantedAccess = 0;
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
entry->GetAccessGranted(&grantedAccess);
|
||||
}
|
||||
|
||||
return OnCacheEntryAvailable(entry, grantedAccess, rv);
|
||||
}
|
||||
|
||||
rv = session->AsyncOpenCacheEntry(mCacheKey, cacheAccess, this, bypassBusy);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
return NS_OK;
|
||||
}
|
||||
@ -625,7 +651,7 @@ _OldCacheLoad::Run()
|
||||
}
|
||||
}
|
||||
|
||||
if (mMainThreadOnly)
|
||||
if (!(mFlags & CHECK_MULTITHREADED))
|
||||
Check();
|
||||
|
||||
// break cycles
|
||||
@ -666,9 +692,12 @@ _OldCacheLoad::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
|
||||
mStatus = status;
|
||||
mNew = access == nsICache::ACCESS_WRITE;
|
||||
|
||||
if (!mMainThreadOnly)
|
||||
if (mFlags & CHECK_MULTITHREADED)
|
||||
Check();
|
||||
|
||||
if (mSync)
|
||||
return Run();
|
||||
|
||||
return NS_DispatchToMainThread(this);
|
||||
}
|
||||
|
||||
|
@ -77,15 +77,16 @@ private:
|
||||
|
||||
nsCOMPtr<nsIEventTarget> mCacheThread;
|
||||
|
||||
nsCString mScheme;
|
||||
nsCString mCacheKey;
|
||||
nsCString const mScheme;
|
||||
nsCString const mCacheKey;
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
|
||||
nsCOMPtr<nsILoadContextInfo> mLoadInfo;
|
||||
uint32_t mFlags;
|
||||
uint32_t const mFlags;
|
||||
|
||||
bool const mWriteToDisk : 1;
|
||||
bool mMainThreadOnly : 1;
|
||||
bool mNew : 1;
|
||||
bool mOpening : 1;
|
||||
bool mSync : 1;
|
||||
|
||||
nsCOMPtr<nsICacheEntry> mCacheEntry;
|
||||
nsresult mStatus;
|
||||
|
@ -7,13 +7,17 @@
|
||||
interface nsICacheEntry;
|
||||
interface nsIApplicationCache;
|
||||
|
||||
[scriptable, uuid(cdd8b9be-71f0-4b0a-a7f4-626fbb3d2e9b)]
|
||||
[scriptable, uuid(1fc9fe11-c6ac-4748-94bd-8555a5a12b94)]
|
||||
interface nsICacheEntryOpenCallback : nsISupports
|
||||
{
|
||||
/**
|
||||
* State of the entry determined by onCacheEntryCheck.
|
||||
*
|
||||
* ENTRY_WANTED - the consumer is interested in the entry, we will pass it.
|
||||
* RECHECK_AFTER_WRITE_FINISHED - the consumer cannot use the entry while data is
|
||||
* still being written and wants to check it again after the current write is
|
||||
* finished. This actually prevents concurrent read/write and is used with
|
||||
* non-resumable HTTP responses.
|
||||
* ENTRY_NEEDS_REVALIDATION - entry needs to be revalidated first with origin server,
|
||||
* this means the loading channel will decide whether to use the entry content
|
||||
* as is after it gets a positive response from the server about validity of the
|
||||
@ -23,12 +27,22 @@ interface nsICacheEntryOpenCallback : nsISupports
|
||||
* ENTRY_NOT_WANTED - the consumer is not interested in the entry, we will not pass it.
|
||||
*/
|
||||
const unsigned long ENTRY_WANTED = 0;
|
||||
const unsigned long ENTRY_NEEDS_REVALIDATION = 1;
|
||||
const unsigned long ENTRY_NOT_WANTED = 2;
|
||||
const unsigned long RECHECK_AFTER_WRITE_FINISHED = 1;
|
||||
const unsigned long ENTRY_NEEDS_REVALIDATION = 2;
|
||||
const unsigned long ENTRY_NOT_WANTED = 3;
|
||||
|
||||
/**
|
||||
* Callback to perform any validity checks before the entry should be used.
|
||||
* Called before onCacheEntryAvailable callback.
|
||||
* Called before onCacheEntryAvailable callback, depending on the result it
|
||||
* may be called more then one time.
|
||||
*
|
||||
* This callback is ensured to be called on the same thread on which asyncOpenURI
|
||||
* has been called, unless nsICacheStorage.CHECK_MULTITHREADED flag has been specified.
|
||||
* In that case this callback can be invoked on any thread, usually it is the cache I/O
|
||||
* or cache management thread.
|
||||
*
|
||||
* IMPORTANT NOTE:
|
||||
* This callback may be invoked sooner then respective asyncOpenURI call exits.
|
||||
*
|
||||
* @param aEntry
|
||||
* An entry to examine. Consumer has a chance to decide whether the
|
||||
@ -37,15 +51,18 @@ interface nsICacheEntryOpenCallback : nsISupports
|
||||
* Optional, application cache the entry has been found in, if any.
|
||||
* @return
|
||||
* State of the entry, see the constants just above.
|
||||
*
|
||||
* NOTE: This callback is invoked on the cache background thread.
|
||||
*/
|
||||
unsigned long onCacheEntryCheck(in nsICacheEntry aEntry,
|
||||
in nsIApplicationCache aApplicationCache);
|
||||
|
||||
/**
|
||||
* Callback implemented by consumers of nsICacheStorage fetching
|
||||
* result of the cache async open request.
|
||||
* Callback giving actual result of asyncOpenURI. It may give consumer the cache
|
||||
* entry or a failure result when it's not possible to open it from some reason.
|
||||
* This callback is ensured to be called on the same thread on which asyncOpenURI
|
||||
* has been called.
|
||||
*
|
||||
* IMPORTANT NOTE:
|
||||
* This callback may be invoked sooner then respective asyncOpenURI call exits.
|
||||
*
|
||||
* @param aEntry
|
||||
* The entry bound to the originally requested URI. May be null when
|
||||
@ -60,24 +77,15 @@ interface nsICacheEntryOpenCallback : nsISupports
|
||||
* given application cache. It should be associated with the loading
|
||||
* channel.
|
||||
* @param aResult
|
||||
* Result of request. This may be a failure only when one of these
|
||||
* Result of the request. This may be a failure only when one of these
|
||||
* issues occur:
|
||||
* - the cache storage service could not be started due to some unexpected
|
||||
* faulure
|
||||
* - there is not enough disk space to create new entries
|
||||
* - cache entry was not found in a given application cache
|
||||
*
|
||||
* NOTE: In the current implementation this callback is invoked on the main thread
|
||||
* however, we would like to call this on a different thread in the future.
|
||||
*/
|
||||
void onCacheEntryAvailable(in nsICacheEntry aEntry,
|
||||
in boolean aNew,
|
||||
in nsIApplicationCache aApplicationCache,
|
||||
in nsresult aResult);
|
||||
|
||||
/**
|
||||
* Whether this callback can be invoked on any thread, or just on the main thread
|
||||
* when the consumer is e.g. a JS.
|
||||
*/
|
||||
readonly attribute boolean mainThreadOnly;
|
||||
};
|
||||
|
@ -38,13 +38,17 @@ interface nsICacheStorage : nsISupports
|
||||
const uint32_t OPEN_PRIORITY = 1 << 2;
|
||||
|
||||
/**
|
||||
* BACKWARD COMPATIBILITY ONLY
|
||||
*
|
||||
* Reflects LOAD_BYPASS_LOCAL_CACHE_IF_BUSY. Only used for the old
|
||||
* backend compatibility. Doesn't have any mening in the new
|
||||
* implementation.
|
||||
* Bypass the cache load when write is still in progress.
|
||||
*/
|
||||
const uint32_t OPEN_BYPASS_IF_BUSY = 1 << 31;
|
||||
const uint32_t OPEN_BYPASS_IF_BUSY = 1 << 3;
|
||||
|
||||
/**
|
||||
* Perform the cache entry check (onCacheEntryCheck invocation) on any thread
|
||||
* for optimal perfomance optimization. If this flag is not specified it is
|
||||
* ensured that onCacheEntryCheck is called on the same thread as respective
|
||||
* asyncOpen has been called.
|
||||
*/
|
||||
const uint32_t CHECK_MULTITHREADED = 1 << 4;
|
||||
|
||||
/**
|
||||
* Asynchronously opens a cache entry for the specified URI.
|
||||
@ -63,6 +67,8 @@ interface nsICacheStorage : nsISupports
|
||||
* OPEN_READONLY - don't create an entry if there is none
|
||||
* OPEN_PRIORITY - give this request a priority over others
|
||||
* OPEN_BYPASS_IF_BUSY - backward compatibility only, LOAD_BYPASS_LOCAL_CACHE_IF_BUSY
|
||||
* CHECK_MULTITHREADED - onCacheEntryCheck may be called on any thread, consumer
|
||||
* implementation is thread-safe
|
||||
* @param aCallback
|
||||
* The consumer that receives the result.
|
||||
* IMPORTANT: The callback may be called sooner the method returns.
|
||||
|
@ -2538,7 +2538,8 @@ nsHttpChannel::OpenCacheEntry(bool usingSSL)
|
||||
cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
|
||||
}
|
||||
else {
|
||||
cacheEntryOpenFlags = nsICacheStorage::OPEN_NORMALLY;
|
||||
cacheEntryOpenFlags = nsICacheStorage::OPEN_NORMALLY
|
||||
| nsICacheStorage::CHECK_MULTITHREADED;
|
||||
}
|
||||
|
||||
if (mApplicationCache) {
|
||||
@ -3144,16 +3145,6 @@ nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(nsICacheEntry *aEntry,
|
||||
return aEntryStatus;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::GetMainThreadOnly(bool *aMainThreadOnly)
|
||||
{
|
||||
NS_ENSURE_ARG(aMainThreadOnly);
|
||||
|
||||
// This implementation accepts callbacks on any thread
|
||||
*aMainThreadOnly = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Generates the proper cache-key for this instance of nsHttpChannel
|
||||
nsresult
|
||||
nsHttpChannel::GenerateCacheKey(uint32_t postID, nsACString &cacheKey)
|
||||
|
@ -85,10 +85,6 @@ function asyncOpenCacheEntry(key, where, flags, lci, callback, appcache)
|
||||
callback(status, entry, appCache);
|
||||
},
|
||||
|
||||
get mainThreadOnly() {
|
||||
return true;
|
||||
},
|
||||
|
||||
run: function () {
|
||||
var storage = getCacheStorage(where, lci, this._appCache);
|
||||
storage.asyncOpenURI(key, "", flags, this);
|
||||
|
@ -41,6 +41,8 @@ const METAONLY = 1 << 9;
|
||||
const RECREATE = 1 << 10;
|
||||
// Do not give me the entry
|
||||
const NOTWANTED = 1 << 11;
|
||||
// Tell the cache to wait for the entry to be completely written first
|
||||
const COMPLETE = 1 << 12;
|
||||
|
||||
var log_c2 = true;
|
||||
function LOG_C2(o, m)
|
||||
@ -117,10 +119,26 @@ OpenCallback.prototype =
|
||||
do_check_neq(this.behavior & (REVAL|PARTIAL), REVAL|PARTIAL);
|
||||
|
||||
if (this.behavior & (REVAL|PARTIAL)) {
|
||||
LOG_C2(this, "onCacheEntryCheck DONE, return REVAL");
|
||||
LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_NEEDS_REVALIDATION");
|
||||
return Ci.nsICacheEntryOpenCallback.ENTRY_NEEDS_REVALIDATION;
|
||||
}
|
||||
|
||||
if (this.behavior & COMPLETE) {
|
||||
LOG_C2(this, "onCacheEntryCheck DONE, return RECHECK_AFTER_WRITE_FINISHED");
|
||||
if (newCacheBackEndUsed()) {
|
||||
// Specific to the new backend because of concurrent read/write:
|
||||
// when a consumer returns RECHECK_AFTER_WRITE_FINISHED from onCacheEntryCheck
|
||||
// the cache calls this callback again after the entry write has finished.
|
||||
// This gives the consumer a chance to recheck completeness of the entry
|
||||
// again.
|
||||
// Thus, we reset state as onCheck would have never been called.
|
||||
this.onCheckPassed = false;
|
||||
// Don't return RECHECK_AFTER_WRITE_FINISHED on second call of onCacheEntryCheck.
|
||||
this.behavior &= ~COMPLETE;
|
||||
}
|
||||
return Ci.nsICacheEntryOpenCallback.RECHECK_AFTER_WRITE_FINISHED;
|
||||
}
|
||||
|
||||
LOG_C2(this, "onCacheEntryCheck DONE, return ENTRY_WANTED");
|
||||
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
|
||||
},
|
||||
@ -218,9 +236,6 @@ OpenCallback.prototype =
|
||||
});
|
||||
}
|
||||
},
|
||||
get mainThreadOnly() {
|
||||
return true;
|
||||
},
|
||||
selfCheck: function()
|
||||
{
|
||||
LOG_C2(this, "selfCheck");
|
||||
|
@ -0,0 +1,39 @@
|
||||
function run_test()
|
||||
{
|
||||
do_get_profile();
|
||||
|
||||
asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
|
||||
new OpenCallback(NEW, "x1m", "x1d", function(entry) {
|
||||
// nothing to do here, we expect concurent callbacks to get
|
||||
// all notified, then the test finishes
|
||||
})
|
||||
);
|
||||
|
||||
var mc = new MultipleCallbacks(3, finish_cache2_test);
|
||||
|
||||
var order = 0;
|
||||
|
||||
asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
|
||||
new OpenCallback(NORMAL|COMPLETE, "x1m", "x1d", function(entry) {
|
||||
++order;
|
||||
do_check_eq(order, newCacheBackEndUsed() ? 3 : 1);
|
||||
mc.fired();
|
||||
})
|
||||
);
|
||||
asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
|
||||
new OpenCallback(NORMAL, "x1m", "x1d", function(entry) {
|
||||
++order;
|
||||
do_check_eq(order, newCacheBackEndUsed() ? 1 : 2);
|
||||
mc.fired();
|
||||
})
|
||||
);
|
||||
asyncOpenCacheEntry("http://x/", "disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
|
||||
new OpenCallback(NORMAL, "x1m", "x1d", function(entry) {
|
||||
++order;
|
||||
do_check_eq(order, newCacheBackEndUsed() ? 2 : 3);
|
||||
mc.fired();
|
||||
})
|
||||
);
|
||||
|
||||
do_test_pending();
|
||||
}
|
@ -39,6 +39,7 @@ skip-if = os == "android"
|
||||
[test_cache2-12-evict-disk.js]
|
||||
[test_cache2-13-evict-non-existing.js]
|
||||
[test_cache2-14-concurent-readers.js]
|
||||
[test_cache2-14b-concurent-readers-complete.js]
|
||||
[test_cache2-15-conditional-304.js]
|
||||
[test_cache2-16-conditional-200.js]
|
||||
[test_cache2-17-evict-all.js]
|
||||
|
Loading…
x
Reference in New Issue
Block a user