Bug 513008 - Eliminate synchronous reads from cache. r=jduell.mcbugs@gmail.com, cbiesinger@gmail.com, sr=shaver@mozilla.org
☠☠ backed out by b4ad5540f2c1 ☠ ☠
authorMichal Novotny <michal.novotny@gmail.com>
Thu, 29 Jul 2010 16:23:10 -0700
changeset 48389 1e89617828246e7240d47c0a943dfa94b1b3acea
parent 48388 e1e1e7db3df5dbf0d0d546521f7883c8f3099863
child 48390 4214929901439e71c0a23b2b518d70dbb49f8270
child 48392 b4ad5540f2c19884cb4edb175b5d723434c8c5da
push id14707
push userjst@mozilla.com
push dateThu, 29 Jul 2010 23:29:18 +0000
treeherderautoland@1e8961782824 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell, shaver
bugs513008
milestone2.0b3pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 513008 - Eliminate synchronous reads from cache. r=jduell.mcbugs@gmail.com, cbiesinger@gmail.com, sr=shaver@mozilla.org
netwerk/cache/nsCacheRequest.h
netwerk/cache/nsCacheService.cpp
netwerk/cache/nsCacheService.h
netwerk/cache/nsICacheService.idl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
--- a/netwerk/cache/nsCacheRequest.h
+++ b/netwerk/cache/nsCacheRequest.h
@@ -49,16 +49,17 @@
 #include "nsCacheService.h"
 
 
 class nsCacheRequest : public PRCList
 {
 private:
     friend class nsCacheService;
     friend class nsCacheEntry;
+    friend class nsProcessRequestEvent;
 
     nsCacheRequest( nsCString *           key, 
                     nsICacheListener *    listener,
                     nsCacheAccessMode     accessRequested,
                     PRBool                blockingMode,
                     nsCacheSession *      session)
         : mKey(key),
           mInfo(0),
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -609,16 +609,55 @@ nsCacheProfilePrefObserver::MemoryCacheC
         capacity   *= 1024;
     } else {
         capacity    = 0;
     }
 
     return capacity;
 }
 
+
+/******************************************************************************
+ * nsProcessRequestEvent
+ *****************************************************************************/
+
+class nsProcessRequestEvent : public nsRunnable {
+public:
+    nsProcessRequestEvent(nsCacheRequest *aRequest)
+    {
+        mRequest = aRequest;
+    }
+
+    NS_IMETHOD Run()
+    {
+        nsresult rv;
+
+        NS_ASSERTION(mRequest->mListener,
+                     "Sync OpenCacheEntry() posted to background thread!");
+
+        nsCacheServiceAutoLock lock;
+        rv = nsCacheService::gService->ProcessRequest(mRequest,
+                                                      PR_FALSE,
+                                                      nsnull);
+
+        // Don't delete the request if it was queued
+        if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
+            delete mRequest;
+
+        return NS_OK;
+    }
+
+protected:
+    virtual ~nsProcessRequestEvent() {}
+
+private:
+    nsCacheRequest *mRequest;
+};
+
+
 /******************************************************************************
  * nsCacheService
  *****************************************************************************/
 #ifdef XP_MAC
 #pragma mark -
 #pragma mark nsCacheService
 #endif
 
@@ -676,18 +715,23 @@ nsCacheService::Init()
     if (mInitialized)
         return NS_ERROR_ALREADY_INITIALIZED;
 
     if (mLock == nsnull)
         return NS_ERROR_OUT_OF_MEMORY;
 
     CACHE_LOG_INIT();
 
+    nsresult rv = NS_NewThread(getter_AddRefs(mCacheIOThread));
+    if (NS_FAILED(rv)) {
+        NS_WARNING("Can't create cache IO thread");
+    }
+
     // initialize hashtable for active cache entries
-    nsresult rv = mActiveEntries.Init();
+    rv = mActiveEntries.Init();
     if (NS_FAILED(rv)) return rv;
     
     // create profile/preference observer
     mObserver = new nsCacheProfilePrefObserver();
     if (!mObserver)  return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(mObserver);
     
     mObserver->Install();
@@ -698,16 +742,19 @@ nsCacheService::Init()
     mInitialized = PR_TRUE;
     return NS_OK;
 }
 
 
 void
 nsCacheService::Shutdown()
 {
+    nsCOMPtr<nsIThread> cacheIOThread;
+
+    {
     nsCacheServiceAutoLock lock;
     NS_ASSERTION(mInitialized, 
                  "can't shutdown nsCacheService unless it has been initialized.");
 
     if (mInitialized) {
 
         mInitialized = PR_FALSE;
 
@@ -729,17 +776,23 @@ nsCacheService::Shutdown()
 
 #ifdef NECKO_OFFLINE_CACHE
         NS_IF_RELEASE(mOfflineDevice);
 #endif // !NECKO_OFFLINE_CACHE
 
 #if defined(NECKO_DISK_CACHE) && defined(PR_LOGGING)
         LogCacheStatistics();
 #endif
+
+        mCacheIOThread.swap(cacheIOThread);
     }
+    } // lock
+
+    if (cacheIOThread)
+        cacheIOThread->Shutdown();
 }
 
 
 nsresult
 nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
 {
     nsresult  rv;
 
@@ -942,16 +995,27 @@ NS_IMETHODIMP nsCacheService::VisitEntri
 }
 
 
 NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
 {
     return  EvictEntriesForClient(nsnull, storagePolicy);
 }
 
+NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
+{
+    nsCacheServiceAutoLock lock;
+
+    if (!mCacheIOThread)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
+    return NS_OK;
+}
+
 /**
  * Internal Methods
  */
 nsresult
 nsCacheService::CreateDiskDevice()
 {
 #ifdef NECKO_DISK_CACHE
     if (!mInitialized)      return NS_ERROR_NOT_AVAILABLE;
@@ -1230,21 +1294,38 @@ nsCacheService::OpenCacheEntry(nsCacheSe
                                           accessRequested,
                                           blockingMode,
                                           listener,
                                           &request);
     if (NS_FAILED(rv))  return rv;
 
     CACHE_LOG_DEBUG(("Created request %p\n", request));
 
-    rv = gService->ProcessRequest(request, PR_TRUE, result);
+    // Process the request on the background thread if we are on the main thread
+    // and the the request is asynchronous
+    if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
+        nsCOMPtr<nsIRunnable> ev =
+            new nsProcessRequestEvent(request);
+        if (ev) {
+            rv = gService->mCacheIOThread->Dispatch(ev, NS_DISPATCH_NORMAL);
+        } else {
+            rv = NS_ERROR_OUT_OF_MEMORY;
+        }
 
-    // delete requests that have completed
-    if (!(listener && (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
-        delete request;
+        // delete request if we didn't post the event
+        if (NS_FAILED(rv))
+            delete request;
+    }
+    else {
+        rv = gService->ProcessRequest(request, PR_TRUE, result);
+
+        // delete requests that have completed
+        if (!(listener && (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
+            delete request;
+    }
 
     return rv;
 }
 
 
 nsresult
 nsCacheService::ActivateEntry(nsCacheRequest * request, 
                               nsCacheEntry ** result)
@@ -1774,17 +1855,17 @@ nsCacheService::Unlock()
 
 void
 nsCacheService::ReleaseObject_Locked(nsISupports * obj,
                                      nsIEventTarget * target)
 {
     NS_ASSERTION(gService->mLockedThread == PR_GetCurrentThread(), "oops");
 
     PRBool isCur;
-    if (!target || NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur) {
+    if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
         gService->mDoomedObjects.AppendElement(obj);
     } else {
         NS_ProxyRelease(target, obj);
     }
 }
 
 
 nsresult
--- a/netwerk/cache/nsCacheService.h
+++ b/netwerk/cache/nsCacheService.h
@@ -161,16 +161,17 @@ public:
 
     static void      OnEnterExitPrivateBrowsing();
 
     nsresult         Init();
     void             Shutdown();
 private:
     friend class nsCacheServiceAutoLock;
     friend class nsOfflineCacheDevice;
+    friend class nsProcessRequestEvent;
 
     /**
      * Internal Methods
      */
 
     static void      Lock();
     static void      Unlock();
 
@@ -240,16 +241,18 @@ private:
     nsCacheProfilePrefObserver *    mObserver;
     
     PRLock *                        mLock;
 
 #if defined(DEBUG)
     PRThread *                      mLockedThread;  // The thread holding mLock
 #endif
 
+    nsCOMPtr<nsIThread>             mCacheIOThread;
+
     nsTArray<nsISupports*>          mDoomedObjects;
     
     PRBool                          mInitialized;
     
     PRBool                          mEnableMemoryDevice;
     PRBool                          mEnableDiskDevice;
     PRBool                          mEnableOfflineDevice;
 
--- a/netwerk/cache/nsICacheService.idl
+++ b/netwerk/cache/nsICacheService.idl
@@ -42,18 +42,19 @@
 
 #include "nsISupports.idl"
 #include "nsICache.idl"
 
 interface nsISimpleEnumerator;
 interface nsICacheListener;
 interface nsICacheSession;
 interface nsICacheVisitor;
+interface nsIEventTarget;
 
-[scriptable, uuid(de114eb4-29fc-4959-b2f7-2d03eb9bc771)]
+[scriptable, uuid(14dbe1e9-f3bc-45af-92f4-2c574fcd4e39)]
 interface nsICacheService : nsISupports
 {
     /**
      * Create a cache session
      *
      * A cache session represents a client's access into the cache.  The cache
      * session is not "owned" by the cache service.  Hence, it is possible to
      * create duplicate cache sessions.  Entries created by a cache session
@@ -82,16 +83,21 @@ interface nsICacheService : nsISupports
 
     /**
      * Evicts all entries in all devices implied by the storage policy.
      *
      * @note This function may evict some items but will throw if it fails to evict
      *       everything.
      */
     void evictEntries(in nsCacheStoragePolicy  storagePolicy);
+
+    /**
+     * Event target which is used for I/O operations
+     */
+    readonly attribute nsIEventTarget cacheIOTarget;
 };
 
 %{C++
 /**
  * Observer service notification that is sent when
  * nsICacheService::evictEntries() or nsICacheSession::evictEntries()
  * is called.
  */
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -81,16 +81,18 @@ static NS_DEFINE_CID(kStreamListenerTeeC
 // nsHttpChannel <public>
 //-----------------------------------------------------------------------------
 
 nsHttpChannel::nsHttpChannel()
     : mLogicalOffset(0)
     , mCacheAccess(0)
     , mPostID(0)
     , mRequestTime(0)
+    , mOnCacheEntryAvailableCallback(nsnull)
+    , mAsyncCacheOpen(PR_FALSE)
     , mStartPos(LL_MAXUINT)
     , mPendingAsyncCallOnResume(nsnull)
     , mSuspendCount(0)
     , mApplyConversion(PR_TRUE)
     , mCachedContentIsValid(PR_FALSE)
     , mCachedContentIsPartial(PR_FALSE)
     , mTransactionReplaced(PR_FALSE)
     , mAuthRetryPending(PR_FALSE)
@@ -167,33 +169,31 @@ nsHttpChannel::Connect(PRBool firstTime)
     LOG(("nsHttpChannel::Connect [this=%p]\n", this));
 
     // ensure that we are using a valid hostname
     if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
         return NS_ERROR_UNKNOWN_HOST;
 
     // true when called from AsyncOpen
     if (firstTime) {
-        PRBool delayed = PR_FALSE;
-
         // are we offline?
         PRBool offline = gIOService->IsOffline();
         if (offline)
             mLoadFlags |= LOAD_ONLY_FROM_CACHE;
         else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
             return ResolveProxy();  // Lazily resolve proxy info
 
         // Don't allow resuming when cache must be used
         if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
             LOG(("Resuming from cache is not supported yet"));
             return NS_ERROR_DOCUMENT_NOT_CACHED;
         }
 
         // open a cache entry for this channel...
-        rv = OpenCacheEntry(offline, &delayed);
+        rv = OpenCacheEntry();
 
         if (NS_FAILED(rv)) {
             LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
             // if this channel is only allowed to pull from the cache, then
             // we must fail if we were unable to open a cache entry.
             if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
                 // If we have a fallback URI (and we're not already
                 // falling back), process the fallback asynchronously.
@@ -207,17 +207,17 @@ nsHttpChannel::Connect(PRBool firstTime)
 
         // if cacheForOfflineUse has been set, open up an offline cache
         // entry to update
         if (mCacheForOfflineUse) {
             rv = OpenOfflineCacheEntryForWriting();
             if (NS_FAILED(rv)) return rv;
         }
 
-        if (NS_SUCCEEDED(rv) && delayed)
+        if (NS_SUCCEEDED(rv) && mAsyncCacheOpen)
             return NS_OK;
     }
 
     // we may or may not have a cache entry at this point
     if (mCacheEntry) {
         // inspect the cache entry to determine whether or not we need to go
         // out to net to validate it.  this call sets mCachedContentIsValid
         // and may set request headers as required for cache validation.
@@ -240,16 +240,24 @@ nsHttpChannel::Connect(PRBool firstTime)
         else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
             // the cache contains the requested resource, but it must be 
             // validated before we can reuse it.  since we are not allowed
             // to hit the net, there's nothing more to do.  the document
             // is effectively not in the cache.
             return NS_ERROR_DOCUMENT_NOT_CACHED;
         }
     }
+    else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
+        // If we have a fallback URI (and we're not already
+        // falling back), process the fallback asynchronously.
+        if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
+            return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+        }
+        return NS_ERROR_DOCUMENT_NOT_CACHED;
+    }
 
     // check to see if authorization headers should be included
     mAuthProvider->AddAuthorizationHeaders();
 
     if (mLoadFlags & LOAD_NO_NETWORK_IO) {
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
@@ -1640,21 +1648,21 @@ IsSubRangeRequest(nsHttpRequestHead &aRe
     if (!aRequestHead.PeekHeader(nsHttp::Range))
         return PR_FALSE;
     nsCAutoString byteRange;
     aRequestHead.GetHeader(nsHttp::Range, byteRange);
     return !byteRange.EqualsLiteral("bytes=0-");
 }
 
 nsresult
-nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed)
+nsHttpChannel::OpenCacheEntry()
 {
     nsresult rv;
 
-    *delayed = PR_FALSE;
+    mAsyncCacheOpen = PR_FALSE;
     mLoadedFromApplicationCache = PR_FALSE;
 
     LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
 
     // make sure we're not abusing this function
     NS_PRECONDITION(!mCacheEntry, "cache entry already open");
 
     nsCAutoString cacheKey;
@@ -1680,33 +1688,20 @@ nsHttpChannel::OpenCacheEntry(PRBool off
 
     // Don't cache byte range requests which are subranges, only cache 0-
     // byte range requests.
     if (IsSubRangeRequest(mRequestHead))
         return NS_OK;
 
     GenerateCacheKey(mPostID, cacheKey);
 
-    // Get a cache session with appropriate storage policy
-    nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
-
     // Set the desired cache access mode accordingly...
     nsCacheAccessMode accessRequested;
-    if (offline || (mLoadFlags & INHIBIT_CACHING)) {
-        // If we have been asked to bypass the cache and not write to the
-        // cache, then don't use the cache at all.  Unless we're actually
-        // offline, which takes precedence over BYPASS_LOCAL_CACHE.
-        if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline)
-            return NS_ERROR_NOT_AVAILABLE;
-        accessRequested = nsICache::ACCESS_READ;
-    }
-    else if (BYPASS_LOCAL_CACHE(mLoadFlags))
-        accessRequested = nsICache::ACCESS_WRITE; // replace cache entry
-    else
-        accessRequested = nsICache::ACCESS_READ_WRITE; // normal browsing
+    rv = DetermineCacheAccess(&accessRequested);
+    if NS_FAILED(rv) return rv;
 
     if (!mApplicationCache && mInheritApplicationCache) {
         // Pick up an application cache from the notification
         // callbacks if available
         nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
         GetCallback(appCacheContainer);
 
         if (appCacheContainer) {
@@ -1725,134 +1720,240 @@ nsHttpChannel::OpenCacheEntry(PRBool off
             nsresult rv = appCacheService->ChooseApplicationCache
                 (cacheKey, getter_AddRefs(mApplicationCache));
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     nsCOMPtr<nsICacheSession> session;
 
-    // Will be set to true if we've found the right session, but need
-    // to open the cache entry asynchronously.
-    PRBool waitingForValidation = PR_FALSE;
-
     // If we have an application cache, we check it first.
     if (mApplicationCache) {
         nsCAutoString appCacheClientID;
         mApplicationCache->GetClientID(appCacheClientID);
 
         nsCOMPtr<nsICacheService> serv =
             do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
         NS_ENSURE_SUCCESS(rv, rv);
 
         rv = serv->CreateSession(appCacheClientID.get(),
                                  nsICache::STORE_OFFLINE,
                                  nsICache::STREAM_BASED,
                                  getter_AddRefs(session));
         NS_ENSURE_SUCCESS(rv, rv);
 
-        // we'll try to synchronously open the cache entry... however,
-        // it may be in use and not yet validated, in which case we'll
-        // try asynchronously opening the cache entry.
-        //
-        // We open with ACCESS_READ only, because we don't want to
-        // overwrite the offline cache entry non-atomically.
-        // ACCESS_READ will prevent us from writing to the offline
-        // cache as a normal cache entry.
-        rv = session->OpenCacheEntry(cacheKey,
-                                     nsICache::ACCESS_READ, PR_FALSE,
-                                     getter_AddRefs(mCacheEntry));
-        if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
-            accessRequested = nsICache::ACCESS_READ;
-            waitingForValidation = PR_TRUE;
-            rv = NS_OK;
-        }
-
-        if (NS_FAILED(rv) && !mCacheForOfflineUse && !mFallbackChannel) {
-            // Check for namespace match.
-            nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
-            rv = mApplicationCache->GetMatchingNamespace
-                (cacheKey, getter_AddRefs(namespaceEntry));
-            NS_ENSURE_SUCCESS(rv, rv);
-
-            PRUint32 namespaceType = 0;
-            if (!namespaceEntry ||
-                NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
-                (namespaceType &
-                 (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
-                  nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC |
-                  nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) {
-                // When loading from an application cache, only items
-                // on the whitelist or matching a
-                // fallback/opportunistic namespace should hit the
-                // network...
-                mLoadFlags |= LOAD_ONLY_FROM_CACHE;
-
-                // ... and if there were an application cache entry,
-                // we would have found it earlier.
-                return NS_ERROR_CACHE_KEY_NOT_FOUND;
+        if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
+            // must use synchronous open for LOAD_BYPASS_LOCAL_CACHE_IF_BUSY
+            rv = session->OpenCacheEntry(cacheKey,
+                                         nsICache::ACCESS_READ, PR_FALSE,
+                                         getter_AddRefs(mCacheEntry));
+            if (NS_SUCCEEDED(rv)) {
+                mCacheEntry->GetAccessGranted(&mCacheAccess);
+                LOG(("nsHttpChannel::OpenCacheEntry [this=%p grantedAccess=%d]",
+                    this, mCacheAccess));
+                mLoadedFromApplicationCache = PR_TRUE;
+                return NS_OK;
+            } else if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
+                LOG(("bypassing local cache since it is busy\n"));
+                // Don't try to load normal cache entry
+                return NS_ERROR_NOT_AVAILABLE;
             }
-
-            if (namespaceType &
-                nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
-                rv = namespaceEntry->GetData(mFallbackKey);
-                NS_ENSURE_SUCCESS(rv, rv);
-            }
-
-            if ((namespaceType &
-                 nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC) &&
-                mLoadFlags & LOAD_DOCUMENT_URI) {
-                // Document loads for items in an opportunistic namespace
-                // should be placed in the offline cache.
-                nsCString clientID;
-                mApplicationCache->GetClientID(clientID);
-
-                mCacheForOfflineUse = !clientID.IsEmpty();
-                SetOfflineCacheClientID(clientID);
-                mCachingOpportunistically = PR_TRUE;
+        } else {
+            mOnCacheEntryAvailableCallback =
+                &nsHttpChannel::OnOfflineCacheEntryAvailable;
+            // We open with ACCESS_READ only, because we don't want to
+            // overwrite the offline cache entry non-atomically.
+            // ACCESS_READ will prevent us from writing to the offline
+            // cache as a normal cache entry.
+            rv = session->AsyncOpenCacheEntry(cacheKey,
+                                              nsICache::ACCESS_READ,
+                                              this);
+
+            if (NS_SUCCEEDED(rv)) {
+                mAsyncCacheOpen = PR_TRUE;
+                return NS_OK;
             }
         }
-        else if (NS_SUCCEEDED(rv)) {
-            // We successfully opened an offline cache session and the entry,
-            // now indiciate we load from the offline cache.
-            mLoadedFromApplicationCache = PR_TRUE;
+
+        // sync or async opening failed
+        return OnOfflineCacheEntryAvailable(nsnull, nsICache::ACCESS_NONE,
+                                            rv, PR_TRUE);
+    }
+
+    return OpenNormalCacheEntry(PR_TRUE);
+}
+
+nsresult
+nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
+                                            nsCacheAccessMode aAccess,
+                                            nsresult aEntryStatus,
+                                            PRBool aIsSync)
+{
+    nsresult rv;
+
+    if (NS_SUCCEEDED(aEntryStatus)) {
+        // We successfully opened an offline cache session and the entry,
+        // so indicate we will load from the offline cache.
+        mLoadedFromApplicationCache = PR_TRUE;
+        mCacheEntry = aEntry;
+        mCacheAccess = aAccess;
+    }
+
+    if (mCanceled && NS_FAILED(mStatus)) {
+        LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
+        return mStatus;
+    }
+
+    if (NS_SUCCEEDED(aEntryStatus))
+        // Called from OnCacheEntryAvailable, advance to the next state
+        return Connect(PR_FALSE);
+
+    if (!mCacheForOfflineUse && !mFallbackChannel) {
+        nsCAutoString cacheKey;
+        GenerateCacheKey(mPostID, cacheKey);
+
+        // Check for namespace match.
+        nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
+        rv = mApplicationCache->GetMatchingNamespace
+            (cacheKey, getter_AddRefs(namespaceEntry));
+        if (NS_FAILED(rv) && !aIsSync)
+            return Connect(PR_FALSE);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        PRUint32 namespaceType = 0;
+        if (!namespaceEntry ||
+            NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
+            (namespaceType &
+             (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
+              nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC |
+              nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) {
+            // When loading from an application cache, only items
+            // on the whitelist or matching a
+            // fallback/opportunistic namespace should hit the
+            // network...
+            mLoadFlags |= LOAD_ONLY_FROM_CACHE;
+
+            // ... and if there were an application cache entry,
+            // we would have found it earlier.
+            return aIsSync ? NS_ERROR_CACHE_KEY_NOT_FOUND : Connect(PR_FALSE);
+        }
+
+        if (namespaceType &
+            nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
+            rv = namespaceEntry->GetData(mFallbackKey);
+            if (NS_FAILED(rv) && !aIsSync)
+                return Connect(PR_FALSE);
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
+
+        if ((namespaceType &
+             nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC) &&
+            mLoadFlags & LOAD_DOCUMENT_URI) {
+            // Document loads for items in an opportunistic namespace
+            // should be placed in the offline cache.
+            nsCString clientID;
+            mApplicationCache->GetClientID(clientID);
+
+            mCacheForOfflineUse = !clientID.IsEmpty();
+            SetOfflineCacheClientID(clientID);
+            mCachingOpportunistically = PR_TRUE;
         }
     }
 
-    if (!mCacheEntry && !waitingForValidation) {
-        rv = gHttpHandler->GetCacheSession(storagePolicy,
-                                           getter_AddRefs(session));
-        if (NS_FAILED(rv)) return rv;
-
+    return OpenNormalCacheEntry(aIsSync);
+}
+
+
+nsresult
+nsHttpChannel::OpenNormalCacheEntry(PRBool aIsSync)
+{
+    NS_ASSERTION(!mCacheEntry, "We have already mCacheEntry");
+
+    nsresult rv;
+
+    nsCAutoString cacheKey;
+    GenerateCacheKey(mPostID, cacheKey);
+
+    nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
+
+    nsCOMPtr<nsICacheSession> session;
+    rv = gHttpHandler->GetCacheSession(storagePolicy,
+                                       getter_AddRefs(session));
+    if (NS_FAILED(rv)) return rv;
+
+    nsCacheAccessMode accessRequested;
+    rv = DetermineCacheAccess(&accessRequested);
+    if NS_FAILED(rv) return rv;
+
+    if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
+        if (!aIsSync) {
+            // Unexpected state: we were called from OnCacheEntryAvailable(),
+            // so LOAD_BYPASS_LOCAL_CACHE_IF_BUSY shouldn't be set. Unless
+            // somebody altered mLoadFlags between OpenCacheEntry() and
+            // OnCacheEntryAvailable()...
+            NS_WARNING(
+                "OpenNormalCacheEntry() called from OnCacheEntryAvailable() "
+                "when LOAD_BYPASS_LOCAL_CACHE_IF_BUSY was specified");
+        }
+
+        // must use synchronous open for LOAD_BYPASS_LOCAL_CACHE_IF_BUSY
         rv = session->OpenCacheEntry(cacheKey, accessRequested, PR_FALSE,
                                      getter_AddRefs(mCacheEntry));
-        if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
-            waitingForValidation = PR_TRUE;
-            rv = NS_OK;
+        if (NS_SUCCEEDED(rv)) {
+            mCacheEntry->GetAccessGranted(&mCacheAccess);
+            LOG(("nsHttpChannel::OpenCacheEntry [this=%p grantedAccess=%d]",
+                this, mCacheAccess));
         }
-        if (NS_FAILED(rv)) return rv;
-    }
-
-    if (waitingForValidation) {
-        // access to the cache entry has been denied (because the
-        // cache entry is probably in use by another channel).
-        if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
+        else if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
             LOG(("bypassing local cache since it is busy\n"));
-            return NS_ERROR_NOT_AVAILABLE;
+            rv = NS_ERROR_NOT_AVAILABLE;
         }
+    }
+    else {
+        mOnCacheEntryAvailableCallback =
+            &nsHttpChannel::OnNormalCacheEntryAvailable;
         rv = session->AsyncOpenCacheEntry(cacheKey, accessRequested, this);
-        if (NS_FAILED(rv)) return rv;
-        // we'll have to wait for the cache entry
-        *delayed = PR_TRUE;
+        if (NS_SUCCEEDED(rv)) {
+            mAsyncCacheOpen = PR_TRUE;
+            return NS_OK;
+        }
     }
-    else if (NS_SUCCEEDED(rv)) {
-        mCacheEntry->GetAccessGranted(&mCacheAccess);
-        LOG(("nsHttpChannel::OpenCacheEntry [this=%p grantedAccess=%d]", this, mCacheAccess));
+
+    if (!aIsSync)
+        // Called from OnCacheEntryAvailable, advance to the next state
+        rv = Connect(PR_FALSE);
+
+    return rv;
+}
+
+nsresult
+nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
+                                           nsCacheAccessMode aAccess,
+                                           nsresult aEntryStatus,
+                                           PRBool aIsSync)
+{
+    NS_ASSERTION(!aIsSync, "aIsSync should be false");
+
+    if (NS_SUCCEEDED(aEntryStatus)) {
+        mCacheEntry = aEntry;
+        mCacheAccess = aAccess;
     }
-    return rv;
+
+    if (mCanceled && NS_FAILED(mStatus)) {
+        LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
+        return mStatus;
+    }
+
+    if ((mLoadFlags & LOAD_ONLY_FROM_CACHE) && NS_FAILED(aEntryStatus))
+        // if this channel is only allowed to pull from the cache, then
+        // we must fail if we were unable to open a cache entry.
+        return NS_ERROR_DOCUMENT_NOT_CACHED;
+
+    // advance to the next state...
+    return Connect(PR_FALSE);
 }
 
 
 nsresult
 nsHttpChannel::OpenOfflineCacheEntryForWriting()
 {
     nsresult rv;
 
@@ -2671,30 +2772,36 @@ nsHttpChannel::InstallCacheListener(PRUi
     rv = mCacheEntry->MarkValid();
     if (NS_FAILED(rv)) return rv;
 #endif
 
     nsCOMPtr<nsIStreamListenerTee> tee =
         do_CreateInstance(kStreamListenerTeeCID, &rv);
     if (NS_FAILED(rv)) return rv;
 
+    nsCOMPtr<nsICacheService> serv =
+        do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIEventTarget> cacheIOTarget;
+    serv->GetCacheIOTarget(getter_AddRefs(cacheIOTarget));
+
     nsCacheStoragePolicy policy;
     rv = mCacheEntry->GetStoragePolicy(&policy);
 
-    if (!gHttpHandler->mCacheWriteThread ||
-         NS_FAILED(rv) ||
-         policy == nsICache::STORE_ON_DISK_AS_FILE) {
-        LOG(("nsHttpChannel::InstallCacheListener sync tee %p\n", tee.get()));
+    if (NS_FAILED(rv) || policy == nsICache::STORE_ON_DISK_AS_FILE ||
+        !cacheIOTarget) {
+        LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%x policy=%d "
+             "cacheIOTarget=%p", tee.get(), rv, policy, cacheIOTarget.get()));
         rv = tee->Init(mListener, out, nsnull);
     } else {
-        LOG(("nsHttpChannel::InstallCacheListener async tee %p\n",
-                tee.get()));
-        rv = tee->InitAsync(mListener, gHttpHandler->mCacheWriteThread, out, nsnull);
+        LOG(("nsHttpChannel::InstallCacheListener async tee %p", tee.get()));
+        rv = tee->InitAsync(mListener, cacheIOTarget, out, nsnull);
     }
-   
+
     if (NS_FAILED(rv)) return rv;
     mListener = tee;
     return NS_OK;
 }
 
 nsresult
 nsHttpChannel::InstallOfflineCacheListener()
 {
@@ -4179,45 +4286,32 @@ nsHttpChannel::GetEntityID(nsACString& a
 // nsHttpChannel::nsICacheListener
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
                                      nsCacheAccessMode access,
                                      nsresult status)
 {
+    nsresult rv;
+
     LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
          "access=%x status=%x]\n", this, entry, access, status));
 
     // if the channel's already fired onStopRequest, then we should ignore
     // this event.
     if (!mIsPending)
         return NS_OK;
 
-    // otherwise, we have to handle this event.
-    if (NS_SUCCEEDED(status)) {
-        mCacheEntry = entry;
-        mCacheAccess = access;
-    }
-
-    nsresult rv;
-
-    if (mCanceled && NS_FAILED(mStatus)) {
-        LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
-        rv = mStatus;
-    }
-    else if ((mLoadFlags & LOAD_ONLY_FROM_CACHE) && NS_FAILED(status))
-        // if this channel is only allowed to pull from the cache, then
-        // we must fail if we were unable to open a cache entry.
-        rv = NS_ERROR_DOCUMENT_NOT_CACHED;
-    else
-        // advance to the next state...
-        rv = Connect(PR_FALSE);
-
-    // a failure from Connect means that we have to abort the channel.
+    nsOnCacheEntryAvailableCallback callback = mOnCacheEntryAvailableCallback;
+    mOnCacheEntryAvailableCallback = nsnull;
+
+    NS_ASSERTION(callback,
+        "nsHttpChannel::OnCacheEntryAvailable called without callback");
+    rv = ((*this).*callback)(entry, access, status, PR_FALSE);
     if (NS_FAILED(rv)) {
         CloseCacheEntry(PR_TRUE);
         AsyncAbort(rv);
     }
 
     return NS_OK;
 }
 
@@ -4670,13 +4764,34 @@ nsHttpChannel::DetermineStoragePolicy()
 {
     nsCacheStoragePolicy policy = nsICache::STORE_ANYWHERE;
     if (mLoadFlags & INHIBIT_PERSISTENT_CACHING)
         policy = nsICache::STORE_IN_MEMORY;
 
     return policy;
 }
 
+nsresult
+nsHttpChannel::DetermineCacheAccess(nsCacheAccessMode *_retval)
+{
+    PRBool offline = gIOService->IsOffline();
+
+    if (offline || (mLoadFlags & INHIBIT_CACHING)) {
+        // If we have been asked to bypass the cache and not write to the
+        // cache, then don't use the cache at all.  Unless we're actually
+        // offline, which takes precedence over BYPASS_LOCAL_CACHE.
+        if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline)
+            return NS_ERROR_NOT_AVAILABLE;
+        *_retval = nsICache::ACCESS_READ;
+    }
+    else if (BYPASS_LOCAL_CACHE(mLoadFlags))
+        *_retval = nsICache::ACCESS_WRITE; // replace cache entry
+    else
+        *_retval = nsICache::ACCESS_READ_WRITE; // normal browsing
+
+    return NS_OK;
+}
+
 void
 nsHttpChannel::AsyncOnExamineCachedResponse()
 {
     gHttpHandler->OnExamineCachedResponse(this);
 }
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -212,17 +212,26 @@ private:
     nsresult ProxyFailover();
     nsresult AsyncDoReplaceWithProxy(nsIProxyInfo *);
     nsresult ContinueDoReplaceWithProxy(nsresult);
     void HandleAsyncReplaceWithProxy();
     nsresult ContinueHandleAsyncReplaceWithProxy(nsresult);
     nsresult ResolveProxy();
 
     // cache specific methods
-    nsresult OpenCacheEntry(PRBool offline, PRBool *delayed);
+    nsresult OpenCacheEntry();
+    nsresult OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
+                                          nsCacheAccessMode aAccess,
+                                          nsresult aResult,
+                                          PRBool aSync);
+    nsresult OpenNormalCacheEntry(PRBool aSync);
+    nsresult OnNormalCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
+                                         nsCacheAccessMode aAccess,
+                                         nsresult aResult,
+                                         PRBool aSync);
     nsresult OpenOfflineCacheEntryForWriting();
     nsresult GenerateCacheKey(PRUint32 postID, nsACString &key);
     nsresult UpdateExpirationTime();
     nsresult CheckCache();
     nsresult ShouldUpdateOfflineCacheEntry(PRBool *shouldCacheForOfflineUse);
     nsresult ReadFromCache();
     void     CloseCacheEntry(PRBool doomOnFailure);
     void     CloseOfflineCacheEntry();
@@ -230,16 +239,17 @@ private:
     nsresult InitOfflineCacheEntry();
     nsresult AddCacheEntryHeaders(nsICacheEntryDescriptor *entry);
     nsresult StoreAuthorizationMetaData(nsICacheEntryDescriptor *entry);
     nsresult FinalizeCacheEntry();
     nsresult InstallCacheListener(PRUint32 offset = 0);
     nsresult InstallOfflineCacheListener();
     void     MaybeInvalidateCacheEntryForSubsequentGet();
     nsCacheStoragePolicy DetermineStoragePolicy();
+    nsresult DetermineCacheAccess(nsCacheAccessMode *_retval);
     void     AsyncOnExamineCachedResponse();
 
     // Handle the bogus Content-Encoding Apache sometimes sends
     void ClearBogusContentEncodingIfNeeded();
 
     // byte range request specific methods
     nsresult SetupByteRangeRequest(PRUint32 partialLen);
     nsresult ProcessPartialContent();
@@ -260,16 +270,21 @@ private:
     // cache specific data
     nsCOMPtr<nsICacheEntryDescriptor> mCacheEntry;
     nsRefPtr<nsInputStreamPump>       mCachePump;
     nsAutoPtr<nsHttpResponseHead>     mCachedResponseHead;
     nsCacheAccessMode                 mCacheAccess;
     PRUint32                          mPostID;
     PRUint32                          mRequestTime;
 
+    typedef nsresult (nsHttpChannel:: *nsOnCacheEntryAvailableCallback)(
+        nsICacheEntryDescriptor *, nsCacheAccessMode, nsresult, PRBool);
+    nsOnCacheEntryAvailableCallback   mOnCacheEntryAvailableCallback;
+    PRBool                            mAsyncCacheOpen;
+
     nsCOMPtr<nsICacheEntryDescriptor> mOfflineCacheEntry;
     nsCacheAccessMode                 mOfflineCacheAccess;
     nsCString                         mOfflineCacheClientID;
 
     nsCOMPtr<nsIApplicationCache>     mApplicationCache;
 
     // auth specific data
     nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -286,24 +286,16 @@ nsHttpHandler::Init()
     mSessionStartTime = NowInSeconds();
 
     rv = mAuthCache.Init();
     if (NS_FAILED(rv)) return rv;
 
     rv = InitConnectionMgr();
     if (NS_FAILED(rv)) return rv;
 
-    rv = NS_NewThread(getter_AddRefs(mCacheWriteThread));
-    if (NS_FAILED(rv)) {
-        mCacheWriteThread = nsnull;
-        LOG(("Failed creating cache-write thread - writes will be synchronous"));
-    } else {
-        LOG(("Created cache-write thread = %p", mCacheWriteThread.get()));
-    }
-
     nsCOMPtr<nsIXULAppInfo> appInfo =
         do_GetService("@mozilla.org/xre/app-info;1");
     if (appInfo)
         appInfo->GetPlatformBuildID(mProductSub);
     if (mProductSub.Length() > 8)
         mProductSub.SetLength(8);
 
     // Startup the http category
@@ -313,17 +305,16 @@ nsHttpHandler::Init()
                                   NS_HTTP_STARTUP_TOPIC);    
     
     mObserverService = mozilla::services::GetObserverService();
     if (mObserverService) {
         mObserverService->AddObserver(this, "profile-change-net-teardown", PR_TRUE);
         mObserverService->AddObserver(this, "profile-change-net-restore", PR_TRUE);
         mObserverService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
         mObserverService->AddObserver(this, "net:clear-active-logins", PR_TRUE);
-        mObserverService->AddObserver(this, "xpcom-shutdown-threads", PR_TRUE);
     }
  
     StartPruneDeadConnectionsTimer();
     return NS_OK;
 }
 
 nsresult
 nsHttpHandler::InitConnectionMgr()
@@ -1768,28 +1759,16 @@ nsHttpHandler::Observe(nsISupports *subj
         NS_ASSERTION(timer == mTimer, "unexpected timer-callback");
 #endif
         if (mConnMgr)
             mConnMgr->PruneDeadConnections();
     }
     else if (strcmp(topic, "net:clear-active-logins") == 0) {
         mAuthCache.ClearAll();
     }
-    else if (strcmp(topic, "xpcom-shutdown-threads") == 0) {
-        // Shutdown the cache write thread. This must be done after shutting down
-        // the cache service, because the (memory) cache entries' storage streams
-        // get released on the thread on which they were first written to, which
-        // is this thread.
-        if (mCacheWriteThread) {
-            LOG(("  shutting down cache-write thread...\n"));
-            mCacheWriteThread->Shutdown();
-            LOG(("  cache-write thread shutdown complete\n"));
-            mCacheWriteThread = nsnull;
-        }
-    }
 
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler implementation
 //-----------------------------------------------------------------------------
 
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -205,18 +205,16 @@ public:
         NotifyObservers(chan, NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC);
     }
 
     // Generates the host:port string for use in the Host: header as well as the
     // CONNECT line for proxies. This handles IPv6 literals correctly.
     static nsresult GenerateHostPort(const nsCString& host, PRInt32 port,
                                      nsCString& hostLine);
 
-    // The thread used to implement async cache-writes
-    nsCOMPtr<nsIThread> mCacheWriteThread;
 private:
 
     //
     // Useragent/prefs helper methods
     //
     void     BuildUserAgent();
     void     InitUserAgentComponents();
     void     PrefsChanged(nsIPrefBranch *prefs, const char *pref);