Bug 922741 - make callbacks iteration in CacheEntry smarter, r=michal

This commit is contained in:
Honza Bambas 2013-11-20 23:20:17 +01:00
Родитель 4a18021827
Коммит 2437751f1d
17 изменённых файлов: 474 добавлений и 219 удалений

Просмотреть файл

@ -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]