Bug 744719 - Don't download appcache files one file at a time, r=michal

This commit is contained in:
Honza Bambas 2012-05-29 16:51:20 +02:00
parent 7e40449c93
commit eab20ca0f3
10 changed files with 364 additions and 35 deletions

View File

@ -0,0 +1,37 @@
CACHE MANIFEST
http://mochi.test:8888/tests/SimpleTest/SimpleTest.js
http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js
# more than 15 what is a number of parallel loads
subresource744719.html?001
subresource744719.html?002
subresource744719.html?003
subresource744719.html?004
subresource744719.html?005
subresource744719.html?006
subresource744719.html?007
subresource744719.html?008
subresource744719.html?009
# this one is non existing and should cancel the load
nonexistent744719.html?010
subresource744719.html?011
subresource744719.html?012
subresource744719.html?013
subresource744719.html?014
subresource744719.html?015
subresource744719.html?016
subresource744719.html?017
subresource744719.html?018
subresource744719.html?019
subresource744719.html?020
subresource744719.html?021
subresource744719.html?022
subresource744719.html?023
subresource744719.html?024
subresource744719.html?025
subresource744719.html?026
subresource744719.html?027
subresource744719.html?028
subresource744719.html?029
subresource744719.html?030

View File

@ -0,0 +1,2 @@
Content-Type: text/cache-manifest

View File

@ -0,0 +1,36 @@
CACHE MANIFEST
http://mochi.test:8888/tests/SimpleTest/SimpleTest.js
http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js
# more than 15 what is a number of parallel loads
subresource744719.html?001
subresource744719.html?002
subresource744719.html?003
subresource744719.html?004
subresource744719.html?005
subresource744719.html?006
subresource744719.html?007
subresource744719.html?008
subresource744719.html?009
subresource744719.html?010
subresource744719.html?011
subresource744719.html?012
subresource744719.html?013
subresource744719.html?014
subresource744719.html?015
subresource744719.html?016
subresource744719.html?017
subresource744719.html?018
subresource744719.html?019
subresource744719.html?020
subresource744719.html?021
subresource744719.html?022
subresource744719.html?023
subresource744719.html?024
subresource744719.html?025
subresource744719.html?026
subresource744719.html?027
subresource744719.html?028
subresource744719.html?029
subresource744719.html?030

View File

@ -0,0 +1,2 @@
Content-Type: text/cache-manifest

View File

@ -63,6 +63,13 @@ _TEST_FILES = \
test_bug460353.html \
test_bug474696.html \
test_bug544462.html \
test_bug744719.html \
744719.cacheManifest \
744719.cacheManifest^headers^ \
test_bug744719-cancel.html \
744719-cancel.cacheManifest \
744719-cancel.cacheManifest^headers^ \
subresource744719.html \
test_foreign.html \
test_fallback.html \
test_overlap.html \

View File

@ -0,0 +1 @@
<html><body>Dummy subresource</body></html>

View File

@ -0,0 +1,82 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/744719-cancel.cacheManifest">
<head>
<title>parallel load canceled</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript">
/*
Manifest refers a large number of resource to load. The 10th item however is a reference to a non-existing
resource that cancels the load. This test checks we cancel all loads and don't leak any of the other resources
after cancelation.
*/
ok(applicationCache.mozItems.length == 0,
"applicationCache.mozItems should be available and empty before associating with a cache.");
function updateCanceled()
{
OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/744719-cancel.cacheManifest", false);
OfflineTest.checkCache("http://mochi.test:8888/tests/SimpleTest/SimpleTest.js", false);
OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", false);
OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/nonexistent744719?010", false);
var URL = "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/subresource744719.html?";
OfflineTest.checkCache(URL + "001", false);
OfflineTest.checkCache(URL + "002", false);
OfflineTest.checkCache(URL + "003", false);
OfflineTest.checkCache(URL + "004", false);
OfflineTest.checkCache(URL + "005", false);
OfflineTest.checkCache(URL + "006", false);
OfflineTest.checkCache(URL + "007", false);
OfflineTest.checkCache(URL + "008", false);
OfflineTest.checkCache(URL + "009", false);
OfflineTest.checkCache(URL + "011", false);
OfflineTest.checkCache(URL + "012", false);
OfflineTest.checkCache(URL + "013", false);
OfflineTest.checkCache(URL + "014", false);
OfflineTest.checkCache(URL + "015", false);
OfflineTest.checkCache(URL + "016", false);
OfflineTest.checkCache(URL + "017", false);
OfflineTest.checkCache(URL + "018", false);
OfflineTest.checkCache(URL + "019", false);
OfflineTest.checkCache(URL + "020", false);
OfflineTest.checkCache(URL + "021", false);
OfflineTest.checkCache(URL + "022", false);
OfflineTest.checkCache(URL + "023", false);
OfflineTest.checkCache(URL + "024", false);
OfflineTest.checkCache(URL + "025", false);
OfflineTest.checkCache(URL + "026", false);
OfflineTest.checkCache(URL + "027", false);
OfflineTest.checkCache(URL + "028", false);
OfflineTest.checkCache(URL + "029", false);
OfflineTest.checkCache(URL + "030", false);
OfflineTest.teardown();
OfflineTest.finish();
}
if (OfflineTest.setup()) {
// Wait some time after the update has been canceled to catch potential leaks of channels that would cause
// unwanted items to be cached regardless we have canceled the update.
applicationCache.onerror = setTimeout(OfflineTest.priv(updateCanceled), 1000);
// We don't expect this update to finish correctly.
applicationCache.oncached = OfflineTest.failEvent;
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,76 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/744719.cacheManifest">
<head>
<title>parallel load</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript">
/*
Simply load a large number of resources and check all are properly cached. This should cover all parts
of the parallel loading code.
*/
ok(applicationCache.mozItems.length == 0,
"applicationCache.mozItems should be available and empty before associating with a cache.");
function manifestUpdated()
{
OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/744719.cacheManifest", true);
OfflineTest.checkCache("http://mochi.test:8888/tests/SimpleTest/SimpleTest.js", true);
OfflineTest.checkCache("http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", true);
var URL = "http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/subresource744719.html?";
OfflineTest.checkCache(URL + "001", true);
OfflineTest.checkCache(URL + "002", true);
OfflineTest.checkCache(URL + "003", true);
OfflineTest.checkCache(URL + "004", true);
OfflineTest.checkCache(URL + "005", true);
OfflineTest.checkCache(URL + "006", true);
OfflineTest.checkCache(URL + "007", true);
OfflineTest.checkCache(URL + "008", true);
OfflineTest.checkCache(URL + "009", true);
OfflineTest.checkCache(URL + "010", true);
OfflineTest.checkCache(URL + "011", true);
OfflineTest.checkCache(URL + "012", true);
OfflineTest.checkCache(URL + "013", true);
OfflineTest.checkCache(URL + "014", true);
OfflineTest.checkCache(URL + "015", true);
OfflineTest.checkCache(URL + "016", true);
OfflineTest.checkCache(URL + "017", true);
OfflineTest.checkCache(URL + "018", true);
OfflineTest.checkCache(URL + "019", true);
OfflineTest.checkCache(URL + "020", true);
OfflineTest.checkCache(URL + "021", true);
OfflineTest.checkCache(URL + "022", true);
OfflineTest.checkCache(URL + "023", true);
OfflineTest.checkCache(URL + "024", true);
OfflineTest.checkCache(URL + "025", true);
OfflineTest.checkCache(URL + "026", true);
OfflineTest.checkCache(URL + "027", true);
OfflineTest.checkCache(URL + "028", true);
OfflineTest.checkCache(URL + "029", true);
OfflineTest.checkCache(URL + "030", true);
OfflineTest.teardown();
OfflineTest.finish();
}
if (OfflineTest.setup()) {
applicationCache.onerror = OfflineTest.failEvent;
applicationCache.oncached = OfflineTest.priv(manifestUpdated);
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
</body>
</html>

View File

@ -45,6 +45,8 @@ using namespace mozilla;
static const PRUint32 kRescheduleLimit = 3;
// Max number of retries for every entry of pinned app.
static const PRUint32 kPinnedEntryRetriesLimit = 3;
// Maximum number of parallel items loads
static const PRUint32 kParallelLoadLimit = 15;
#if defined(PR_LOGGING)
//
@ -415,8 +417,6 @@ nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest,
{
LOG(("done fetching offline item [status=%x]\n", aStatus));
mState = nsIDOMLoadStatus::LOADED;
if (mBytesRead == 0 && aStatus == NS_OK) {
// we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
// specified), but the object should report loadedSize as if it
@ -439,7 +439,15 @@ nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest,
NS_IMETHODIMP
nsOfflineCacheUpdateItem::Run()
{
mUpdate->LoadCompleted();
// Set mState to LOADED here rather than in OnStopRequest to prevent
// race condition when checking state of all mItems in ProcessNextURI().
// If state would have been set in OnStopRequest we could mistakenly
// take this item as already finished and finish the update process too
// early when ProcessNextURI() would get called between OnStopRequest()
// and Run() of this item. Finish() would then have been called twice.
mState = nsIDOMLoadStatus::LOADED;
mUpdate->LoadCompleted(this);
return NS_OK;
}
@ -599,6 +607,25 @@ nsOfflineCacheUpdateItem::GetRequestSucceeded(bool * succeeded)
return NS_OK;
}
bool
nsOfflineCacheUpdateItem::IsScheduled()
{
return mState == nsIDOMLoadStatus::UNINITIALIZED;
}
bool
nsOfflineCacheUpdateItem::IsInProgress()
{
return mState == nsIDOMLoadStatus::REQUESTED ||
mState == nsIDOMLoadStatus::RECEIVING;
}
bool
nsOfflineCacheUpdateItem::IsCompleted()
{
return mState == nsIDOMLoadStatus::LOADED;
}
NS_IMETHODIMP
nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus)
{
@ -709,6 +736,7 @@ nsOfflineManifestItem::ReadManifest(nsIInputStream *aInputStream,
if (NS_FAILED(rv)) {
LOG(("HandleManifestLine failed with 0x%08x", rv));
*aBytesConsumed = 0; // Avoid assertion failure in stream tee
return NS_ERROR_ABORT;
}
@ -1096,9 +1124,10 @@ nsOfflineManifestItem::OnStopRequest(nsIRequest *aRequest,
// nsOfflineCacheUpdate::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS2(nsOfflineCacheUpdate,
NS_IMPL_ISUPPORTS3(nsOfflineCacheUpdate,
nsIOfflineCacheUpdateObserver,
nsIOfflineCacheUpdate)
nsIOfflineCacheUpdate,
nsIRunnable)
//-----------------------------------------------------------------------------
// nsOfflineCacheUpdate <public>
@ -1111,7 +1140,7 @@ nsOfflineCacheUpdate::nsOfflineCacheUpdate()
, mPartialUpdate(false)
, mSucceeded(true)
, mObsolete(false)
, mCurrentItem(-1)
, mItemsInProgress(0)
, mRescheduleCount(0)
, mPinnedEntryRetriesCount(0)
, mPinned(false)
@ -1317,7 +1346,7 @@ nsOfflineCacheUpdate::HandleManifest(bool *aDoUpdate)
}
void
nsOfflineCacheUpdate::LoadCompleted()
nsOfflineCacheUpdate::LoadCompleted(nsOfflineCacheUpdateItem *aItem)
{
nsresult rv;
@ -1336,6 +1365,8 @@ nsOfflineCacheUpdate::LoadCompleted()
NS_ASSERTION(mManifestItem,
"Must have a manifest item in STATE_CHECKING.");
NS_ASSERTION(mManifestItem == aItem,
"Unexpected aItem in nsOfflineCacheUpdate::LoadCompleted");
// A 404 or 410 is interpreted as an intentional removal of
// the manifest file, rather than a transient server error.
@ -1403,21 +1434,21 @@ nsOfflineCacheUpdate::LoadCompleted()
}
// Normal load finished.
nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[mCurrentItem];
if (mItemsInProgress) // Just to be safe here!
--mItemsInProgress;
bool succeeded;
rv = item->GetRequestSucceeded(&succeeded);
rv = aItem->GetRequestSucceeded(&succeeded);
if (mPinned) {
if (mPinned && NS_SUCCEEDED(rv) && succeeded) {
PRUint32 dummy_cache_type;
rv = mApplicationCache->GetTypes(item->mCacheKey, &dummy_cache_type);
rv = mApplicationCache->GetTypes(aItem->mCacheKey, &dummy_cache_type);
bool item_doomed = NS_FAILED(rv); // can not find it? -> doomed
if (item_doomed &&
mPinnedEntryRetriesCount < kPinnedEntryRetriesLimit &&
(item->mItemType & (nsIApplicationCache::ITEM_EXPLICIT |
nsIApplicationCache::ITEM_FALLBACK))) {
(aItem->mItemType & (nsIApplicationCache::ITEM_EXPLICIT |
nsIApplicationCache::ITEM_FALLBACK))) {
rv = EvictOneNonPinned();
if (NS_FAILED(rv)) {
mSucceeded = false;
@ -1426,7 +1457,9 @@ nsOfflineCacheUpdate::LoadCompleted()
return;
}
rv = item->Cancel();
// This reverts the item state to UNINITIALIZED that makes it to
// be scheduled for download again.
rv = aItem->Cancel();
if (NS_FAILED(rv)) {
mSucceeded = false;
NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR);
@ -1435,26 +1468,29 @@ nsOfflineCacheUpdate::LoadCompleted()
}
mPinnedEntryRetriesCount++;
// Retry current item, so mCurrentItem is not advanced.
// Retry this item.
ProcessNextURI();
return;
}
}
// Advance to next item.
mCurrentItem++;
// According to parallelism this may imply more pinned retries count,
// but that is not critical, since at one moment the algoritm will
// stop anyway. Also, this code may soon be completely removed
// after we have a separate storage for pinned apps.
mPinnedEntryRetriesCount = 0;
// Check for failures. 3XX, 4XX and 5XX errors on items explicitly
// listed in the manifest will cause the update to fail.
if (NS_FAILED(rv) || !succeeded) {
if (item->mItemType &
if (aItem->mItemType &
(nsIApplicationCache::ITEM_EXPLICIT |
nsIApplicationCache::ITEM_FALLBACK)) {
mSucceeded = false;
}
} else {
rv = mApplicationCache->MarkEntry(item->mCacheKey, item->mItemType);
rv = mApplicationCache->MarkEntry(aItem->mCacheKey, aItem->mItemType);
if (NS_FAILED(rv)) {
mSucceeded = false;
}
@ -1531,7 +1567,7 @@ nsOfflineCacheUpdate::Begin()
// Keep the object alive through a ProcessNextURI()/Finish() call.
nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
mCurrentItem = 0;
mItemsInProgress = 0;
if (mPartialUpdate) {
mState = STATE_DOWNLOADING;
@ -1557,7 +1593,7 @@ nsOfflineCacheUpdate::Begin()
nsresult rv = mManifestItem->OpenChannel();
if (NS_FAILED(rv)) {
LoadCompleted();
LoadCompleted(mManifestItem);
}
return NS_OK;
@ -1571,10 +1607,12 @@ nsOfflineCacheUpdate::Cancel()
mState = STATE_CANCELLED;
mSucceeded = false;
if (mCurrentItem >= 0 &&
mCurrentItem < static_cast<PRInt32>(mItems.Length())) {
// Load might be running
mItems[mCurrentItem]->Cancel();
// Cancel all running downloads
for (PRUint32 i = 0; i < mItems.Length(); ++i) {
nsOfflineCacheUpdateItem * item = mItems[i];
if (item->IsInProgress())
item->Cancel();
}
return NS_OK;
@ -1634,13 +1672,29 @@ nsOfflineCacheUpdate::ProcessNextURI()
// Keep the object alive through a Finish() call.
nsCOMPtr<nsIOfflineCacheUpdate> kungFuDeathGrip(this);
LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, current=%d, numItems=%d]",
this, mCurrentItem, mItems.Length()));
LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, inprogress=%d, numItems=%d]",
this, mItemsInProgress, mItems.Length()));
NS_ASSERTION(mState == STATE_DOWNLOADING,
"ProcessNextURI should only be called from the DOWNLOADING state");
if (mCurrentItem >= static_cast<PRInt32>(mItems.Length())) {
nsOfflineCacheUpdateItem * runItem = nsnull;
PRUint32 completedItems = 0;
for (PRUint32 i = 0; i < mItems.Length(); ++i) {
nsOfflineCacheUpdateItem * item = mItems[i];
if (item->IsScheduled()) {
runItem = item;
break;
}
if (item->IsCompleted())
++completedItems;
}
if (completedItems == mItems.Length()) {
LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]: all items loaded", this));
if (mPartialUpdate) {
return Finish();
} else {
@ -1660,23 +1714,38 @@ nsOfflineCacheUpdate::ProcessNextURI()
}
}
if (!runItem) {
LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]:"
" No more items to include in parallel load", this));
return NS_OK;
}
#if defined(PR_LOGGING)
if (LOG_ENABLED()) {
nsCAutoString spec;
mItems[mCurrentItem]->mURI->GetSpec(spec);
runItem->mURI->GetSpec(spec);
LOG(("%p: Opening channel for %s", this, spec.get()));
}
#endif
++mItemsInProgress;
NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMSTARTED);
nsresult rv = mItems[mCurrentItem]->OpenChannel();
nsresult rv = runItem->OpenChannel();
if (NS_FAILED(rv)) {
LoadCompleted();
LoadCompleted(runItem);
return rv;
}
return NS_OK;
if (mItemsInProgress >= kParallelLoadLimit) {
LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]:"
" At parallel load limit", this));
return NS_OK;
}
// This calls this method again via a post triggering
// a parallel item load
return NS_DispatchToCurrentThread(this);
}
nsresult
@ -2146,3 +2215,14 @@ nsOfflineCacheUpdate::ApplicationCacheAvailable(nsIApplicationCache *application
{
return AssociateDocuments(applicationCache);
}
//-----------------------------------------------------------------------------
// nsOfflineCacheUpdate::nsIRunable
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsOfflineCacheUpdate::Run()
{
ProcessNextURI();
return NS_OK;
}

View File

@ -71,6 +71,10 @@ public:
nsresult Cancel();
nsresult GetRequestSucceeded(bool * succeeded);
bool IsInProgress();
bool IsScheduled();
bool IsCompleted();
private:
nsOfflineCacheUpdate* mUpdate;
nsCOMPtr<nsIChannel> mChannel;
@ -179,12 +183,14 @@ public:
class nsOfflineCacheUpdate : public nsIOfflineCacheUpdate
, public nsIOfflineCacheUpdateObserver
, public nsIRunnable
, public nsOfflineCacheUpdateOwner
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOFFLINECACHEUPDATE
NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
NS_DECL_NSIRUNNABLE
nsOfflineCacheUpdate();
~nsOfflineCacheUpdate();
@ -196,7 +202,7 @@ public:
nsresult Begin();
nsresult Cancel();
void LoadCompleted();
void LoadCompleted(nsOfflineCacheUpdateItem *aItem);
void ManifestCheckCompleted(nsresult aStatus,
const nsCString &aManifestHash);
void StickDocument(nsIURI *aDocumentURI);
@ -260,7 +266,7 @@ private:
nsRefPtr<nsOfflineManifestItem> mManifestItem;
/* Items being updated */
PRInt32 mCurrentItem;
PRUint32 mItemsInProgress;
nsTArray<nsRefPtr<nsOfflineCacheUpdateItem> > mItems;
/* Clients watching this update for changes */