Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
// Regression test for bug 2062281.
//
// If the consumer that puts a cache entry into REVALIDATING never calls
// setValid()/setInvalid() on its handle -- e.g. it is cancelled or crashes
// without releasing it, as opposed to just closing normally, which would
// hit CacheEntry::OnHandleClosed() and revert the state -- the entry must
// not stay wedged in REVALIDATING forever. Every later same-URL consumer
// would otherwise park indefinitely (in production, each such consumer is
// only rescued by nsHttpChannel's independent per-channel
// network.cache.entry_wait_timeout_ms backstop, so every single one of them
// pays that wait). Here we simulate the stranded revalidator by holding its
// handle alive without ever calling setValid(), and verify a later consumer
// completes once the entry has been wedged for longer than
// network.cache.entry_wait_timeout_ms, without needing its own extra wait.
//
// A second scenario below covers the non-wedged success path: if the
// revalidator calls setValid() well within network.cache.entry_wait_timeout_ms,
// a consumer that arrives while the entry is REVALIDATING must be released by
// that setValid() call, not by the self-heal timeout.
function run_test() {
do_get_profile();
Services.prefs.setIntPref("network.cache.entry_wait_timeout_ms", 300);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("network.cache.entry_wait_timeout_ms");
});
asyncOpenCacheEntry(
"disk",
Ci.nsICacheStorage.OPEN_NORMALLY,
null,
new OpenCallback(NEW, "m1", "d1", function () {
// Strand a revalidator: it gets ENTRY_NEEDS_REVALIDATION (entry
// transitions to REVALIDATING) and then just holds onto its handle
// without ever calling setValid()/setInvalid(), simulating a consumer
// that was cancelled or crashed before finishing revalidation.
let strandedHandle = null;
asyncOpenCacheEntry(
"disk",
Ci.nsICacheStorage.OPEN_NORMALLY,
null,
{
QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
onCacheEntryCheck() {
return Ci.nsICacheEntryOpenCallback.ENTRY_NEEDS_REVALIDATION;
},
onCacheEntryAvailable(entry, isNew, status) {
Assert.equal(Cr.NS_OK, status);
strandedHandle = entry;
// Wait past the wedge timeout, then verify a later consumer for
// the same URL is serviced right away, instead of parking
// forever behind the stranded revalidator.
do_timeout(500, function () {
asyncOpenCacheEntry(
"disk",
Ci.nsICacheStorage.OPEN_NORMALLY,
null,
{
QueryInterface: ChromeUtils.generateQI([
"nsICacheEntryOpenCallback",
]),
onCacheEntryCheck() {
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
},
onCacheEntryAvailable(laterEntry, laterIsNew, laterStatus) {
Assert.equal(Cr.NS_OK, laterStatus);
Assert.ok(
!!strandedHandle,
"keep the stranded handle alive until here"
);
run_setvalid_scenario();
},
}
);
});
},
}
);
})
);
do_test_pending();
}
// The revalidator calls setValid() long before the wedge timeout elapses, so
// a consumer waiting behind it must be released by that setValid() call
// rather than by the self-heal timeout.
function run_setvalid_scenario() {
asyncOpenCacheEntry(
"disk",
Ci.nsICacheStorage.OPEN_NORMALLY,
null,
new OpenCallback(NEW, "m2", "d2", function () {
let startTime = Date.now();
asyncOpenCacheEntry(
"disk",
Ci.nsICacheStorage.OPEN_NORMALLY,
null,
new OpenCallback(REVAL, "m2", "d2", function (entry) {
// Validate well before network.cache.entry_wait_timeout_ms (300ms).
do_timeout(50, function () {
entry.setValid();
});
})
);
asyncOpenCacheEntry(
"disk",
Ci.nsICacheStorage.OPEN_NORMALLY,
null,
new OpenCallback(NORMAL, "m2", "d2", function () {
Assert.less(
Date.now() - startTime,
300,
"consumer released by setValid(), not by the wedge self-heal timeout"
);
finish_cache2_test();
})
);
})
);
}