Skip to content
Permalink
Browse files
[WK2] Add initial support for speculative resource revalidation to th…
…e WebKit disk cache

https://bugs.webkit.org/show_bug.cgi?id=150856
<rdar://problem/23092196>

Reviewed by Antti Koivisto.

Add initial support for speculative resource revalidation to the WebKit
disk cache. When a main resource is requested by WebCore, we look up its
list of subresources in the cache and speculatively warm them up before
WebCore asks for them, in order to improve page load time. In this
context, warming it up means:
1. If the cached resource is usable (has not expired), keep it around
   in a memory for a limited amount of time (10 seconds).
2. If the resource has expired, do a speculative revalidation of the
   resource and then keep it in memory for a while as well (10 seconds).

The feature is currently behind a compile-time flag that is only enabled
on COCOA. Also, it is currently disabled at run-time until it is mature
enough.

Initial results show that speculative revalidation gives a ~5%
progression on throttled warm PLT on iPhone 5s.

* NetworkProcess/NetworkResourceLoader.cpp:
(WebKit::NetworkResourceLoader::start):
* NetworkProcess/cache/NetworkCache.cpp:
(WebKit::NetworkCache::Cache::retrieve):
(WebKit::NetworkCache::Cache::store):
(WebKit::NetworkCache::Cache::update):
* NetworkProcess/cache/NetworkCache.h:
* NetworkProcess/cache/NetworkCacheEntry.cpp:
(WebKit::NetworkCache::Entry::Entry):
* NetworkProcess/cache/NetworkCacheEntry.h:
* NetworkProcess/cache/NetworkCacheSpeculativeLoad.cpp: Added.
(WebKit::NetworkCache::SpeculativeLoad::SpeculativeLoad):
(WebKit::NetworkCache::SpeculativeLoad::~SpeculativeLoad):
(WebKit::NetworkCache::SpeculativeLoad::willSendRedirectedRequest):
(WebKit::NetworkCache::SpeculativeLoad::didReceiveBuffer):
(WebKit::NetworkCache::SpeculativeLoad::didFinishLoading):
(WebKit::NetworkCache::SpeculativeLoad::didFailLoading):
(WebKit::NetworkCache::SpeculativeLoad::didComplete):
* NetworkProcess/cache/NetworkCacheSpeculativeLoad.h: Added.
* NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp:
(WebKit::NetworkCache::constructRevalidationRequest):
(WebKit::NetworkCache::responseNeedsRevalidation):
(WebKit::NetworkCache::SpeculativeLoadManager::PreloadedEntry::PreloadedEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::PreloadedEntry::takeCacheEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::PreloadedEntry::lifetimeTimerFired):
(WebKit::NetworkCache::SpeculativeLoadManager::PendingFrameLoad::PendingFrameLoad):
(WebKit::NetworkCache::SpeculativeLoadManager::retrieve):
(WebKit::NetworkCache::SpeculativeLoadManager::registerLoad):
(WebKit::NetworkCache::SpeculativeLoadManager::addPreloadedEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::retrieveEntryFromStorage):
(WebKit::NetworkCache::SpeculativeLoadManager::satisfyPendingRequests):
(WebKit::NetworkCache::SpeculativeLoadManager::revalidateEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::preloadEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::startSpeculativeRevalidation):
(WebKit::NetworkCache::SpeculativeLoadManager::PendingFrameLoad::registerSubresource): Deleted.
(WebKit::NetworkCache::SpeculativeLoadManager::PendingFrameLoad::encodeAsSubresourcesRecord): Deleted.
* NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.h:
* WebKit2.xcodeproj/project.pbxproj:


Canonical link: https://commits.webkit.org/169346@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@192328 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
cdumez committed Nov 11, 2015
1 parent dc49f9a commit 5673c2e2e74a95c76c436cf0f3a894d80db667ad
Showing 11 changed files with 582 additions and 35 deletions.
@@ -1,3 +1,67 @@
2015-11-11 Chris Dumez <cdumez@apple.com>

[WK2] Add initial support for speculative resource revalidation to the WebKit disk cache
https://bugs.webkit.org/show_bug.cgi?id=150856
<rdar://problem/23092196>

Reviewed by Antti Koivisto.

Add initial support for speculative resource revalidation to the WebKit
disk cache. When a main resource is requested by WebCore, we look up its
list of subresources in the cache and speculatively warm them up before
WebCore asks for them, in order to improve page load time. In this
context, warming it up means:
1. If the cached resource is usable (has not expired), keep it around
in a memory for a limited amount of time (10 seconds).
2. If the resource has expired, do a speculative revalidation of the
resource and then keep it in memory for a while as well (10 seconds).

The feature is currently behind a compile-time flag that is only enabled
on COCOA. Also, it is currently disabled at run-time until it is mature
enough.

Initial results show that speculative revalidation gives a ~5%
progression on throttled warm PLT on iPhone 5s.

* NetworkProcess/NetworkResourceLoader.cpp:
(WebKit::NetworkResourceLoader::start):
* NetworkProcess/cache/NetworkCache.cpp:
(WebKit::NetworkCache::Cache::retrieve):
(WebKit::NetworkCache::Cache::store):
(WebKit::NetworkCache::Cache::update):
* NetworkProcess/cache/NetworkCache.h:
* NetworkProcess/cache/NetworkCacheEntry.cpp:
(WebKit::NetworkCache::Entry::Entry):
* NetworkProcess/cache/NetworkCacheEntry.h:
* NetworkProcess/cache/NetworkCacheSpeculativeLoad.cpp: Added.
(WebKit::NetworkCache::SpeculativeLoad::SpeculativeLoad):
(WebKit::NetworkCache::SpeculativeLoad::~SpeculativeLoad):
(WebKit::NetworkCache::SpeculativeLoad::willSendRedirectedRequest):
(WebKit::NetworkCache::SpeculativeLoad::didReceiveBuffer):
(WebKit::NetworkCache::SpeculativeLoad::didFinishLoading):
(WebKit::NetworkCache::SpeculativeLoad::didFailLoading):
(WebKit::NetworkCache::SpeculativeLoad::didComplete):
* NetworkProcess/cache/NetworkCacheSpeculativeLoad.h: Added.
* NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp:
(WebKit::NetworkCache::constructRevalidationRequest):
(WebKit::NetworkCache::responseNeedsRevalidation):
(WebKit::NetworkCache::SpeculativeLoadManager::PreloadedEntry::PreloadedEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::PreloadedEntry::takeCacheEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::PreloadedEntry::lifetimeTimerFired):
(WebKit::NetworkCache::SpeculativeLoadManager::PendingFrameLoad::PendingFrameLoad):
(WebKit::NetworkCache::SpeculativeLoadManager::retrieve):
(WebKit::NetworkCache::SpeculativeLoadManager::registerLoad):
(WebKit::NetworkCache::SpeculativeLoadManager::addPreloadedEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::retrieveEntryFromStorage):
(WebKit::NetworkCache::SpeculativeLoadManager::satisfyPendingRequests):
(WebKit::NetworkCache::SpeculativeLoadManager::revalidateEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::preloadEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::startSpeculativeRevalidation):
(WebKit::NetworkCache::SpeculativeLoadManager::PendingFrameLoad::registerSubresource): Deleted.
(WebKit::NetworkCache::SpeculativeLoadManager::PendingFrameLoad::encodeAsSubresourcesRecord): Deleted.
* NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.h:
* WebKit2.xcodeproj/project.pbxproj:

2015-11-11 Anders Carlsson <andersca@apple.com>

_WKRemoteObjectInterface should handle specifying allowed classes for reply block arguments
@@ -128,7 +128,7 @@ void NetworkResourceLoader::start()
}

RefPtr<NetworkResourceLoader> loader(this);
NetworkCache::singleton().retrieve(originalRequest(), m_parameters.webPageID, m_parameters.webFrameID, [loader](std::unique_ptr<NetworkCache::Entry> entry) {
NetworkCache::singleton().retrieve(originalRequest(), { m_parameters.webPageID, m_parameters.webFrameID }, [loader](std::unique_ptr<NetworkCache::Entry> entry) {
if (loader->hasOneRef()) {
// The loader has been aborted and is only held alive by this lambda.
return;
@@ -241,7 +241,7 @@ auto NetworkResourceLoader::didReceiveResponse(const ResourceResponse& receivedR
if (m_cacheEntryForValidation) {
bool validationSucceeded = m_response.httpStatusCode() == 304; // 304 Not Modified
if (validationSucceeded) {
NetworkCache::singleton().update(originalRequest(), m_parameters.webPageID, *m_cacheEntryForValidation, m_response);
NetworkCache::singleton().update(originalRequest(), { m_parameters.webPageID, m_parameters.webFrameID }, *m_cacheEntryForValidation, m_response);
// If the request was conditional then this revalidation was not triggered by the network cache and we pass the
// 304 response to WebCore.
if (originalRequest().isConditional())
@@ -343,43 +343,55 @@ static StoreDecision makeStoreDecision(const WebCore::ResourceRequest& originalR
return StoreDecision::Yes;
}

void Cache::retrieve(const WebCore::ResourceRequest& originalRequest, uint64_t webPageID, uint64_t webFrameID, std::function<void (std::unique_ptr<Entry>)> completionHandler)
void Cache::retrieve(const WebCore::ResourceRequest& originalRequest, const GlobalFrameID& frameID, std::function<void (std::unique_ptr<Entry>)> completionHandler)
{
ASSERT(isEnabled());
ASSERT(originalRequest.url().protocolIsInHTTPFamily());

LOG(NetworkCache, "(NetworkProcess) retrieving %s priority %d", originalRequest.url().string().ascii().data(), static_cast<int>(originalRequest.priority()));

if (m_statistics)
m_statistics->recordRetrievalRequest(webPageID);
m_statistics->recordRetrievalRequest(frameID.first);

Key storageKey = makeCacheKey(originalRequest);

#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
if (m_speculativeLoadManager)
m_speculativeLoadManager->registerLoad(webPageID, webFrameID, originalRequest, storageKey);
#else
UNUSED_PARAM(webFrameID);
if (m_speculativeLoadManager) {
m_speculativeLoadManager->registerLoad(frameID, originalRequest, storageKey);
RunLoop::main().dispatch([this, originalRequest, frameID, storageKey] {
m_speculativeLoadManager->startSpeculativeRevalidation(originalRequest, frameID, storageKey);
});
}
#endif

auto retrieveDecision = makeRetrieveDecision(originalRequest);
if (retrieveDecision != RetrieveDecision::Yes) {
if (m_statistics)
m_statistics->recordNotUsingCacheForRequest(webPageID, storageKey, originalRequest, retrieveDecision);
m_statistics->recordNotUsingCacheForRequest(frameID.first, storageKey, originalRequest, retrieveDecision);

completionHandler(nullptr);
return;
}

#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
if (m_speculativeLoadManager && m_speculativeLoadManager->retrieve(storageKey, [originalRequest, completionHandler](std::unique_ptr<Entry> entry) {
if (entry && verifyVaryingRequestHeaders(entry->varyingRequestHeaders(), originalRequest))
completionHandler(WTF::move(entry));
else
completionHandler(nullptr);
}))
return;
#endif

auto startTime = std::chrono::system_clock::now();
auto priority = static_cast<unsigned>(originalRequest.priority());

m_storage->retrieve(storageKey, priority, [this, originalRequest, completionHandler, startTime, storageKey, webPageID](std::unique_ptr<Storage::Record> record) {
m_storage->retrieve(storageKey, priority, [this, originalRequest, completionHandler, startTime, storageKey, frameID](std::unique_ptr<Storage::Record> record) {
if (!record) {
LOG(NetworkCache, "(NetworkProcess) not found in storage");

if (m_statistics)
m_statistics->recordRetrievalFailure(webPageID, storageKey, originalRequest);
m_statistics->recordRetrievalFailure(frameID.first, storageKey, originalRequest);

completionHandler(nullptr);
return false;
@@ -407,12 +419,12 @@ void Cache::retrieve(const WebCore::ResourceRequest& originalRequest, uint64_t w
completionHandler(WTF::move(entry));

if (m_statistics)
m_statistics->recordRetrievedCachedEntry(webPageID, storageKey, originalRequest, useDecision);
m_statistics->recordRetrievedCachedEntry(frameID.first, storageKey, originalRequest, useDecision);
return useDecision != UseDecision::NoDueToDecodeFailure;
});
}

void Cache::store(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData, std::function<void (MappedBody&)> completionHandler)
std::unique_ptr<Entry> Cache::store(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData, std::function<void (MappedBody&)> completionHandler)
{
ASSERT(isEnabled());
ASSERT(responseData);
@@ -440,12 +452,12 @@ void Cache::store(const WebCore::ResourceRequest& originalRequest, const WebCore
if (m_statistics)
m_statistics->recordNotCachingResponse(key, storeDecision);

return;
return nullptr;
}

Entry cacheEntry(makeCacheKey(originalRequest), response, WTF::move(responseData), collectVaryingRequestHeaders(originalRequest, response));
std::unique_ptr<Entry> cacheEntry = std::make_unique<Entry>(makeCacheKey(originalRequest), response, WTF::move(responseData), collectVaryingRequestHeaders(originalRequest, response));

auto record = cacheEntry.encodeAsStorageRecord();
auto record = cacheEntry->encodeAsStorageRecord();

m_storage->store(record, [completionHandler](const Data& bodyData) {
MappedBody mappedBody;
@@ -459,23 +471,27 @@ void Cache::store(const WebCore::ResourceRequest& originalRequest, const WebCore
completionHandler(mappedBody);
LOG(NetworkCache, "(NetworkProcess) stored");
});

return cacheEntry;
}

void Cache::update(const WebCore::ResourceRequest& originalRequest, uint64_t webPageID, const Entry& existingEntry, const WebCore::ResourceResponse& validatingResponse)
std::unique_ptr<Entry> Cache::update(const WebCore::ResourceRequest& originalRequest, const GlobalFrameID& frameID, const Entry& existingEntry, const WebCore::ResourceResponse& validatingResponse)
{
LOG(NetworkCache, "(NetworkProcess) updating %s", originalRequest.url().string().latin1().data());

WebCore::ResourceResponse response = existingEntry.response();
WebCore::updateResponseHeadersAfterRevalidation(response, validatingResponse);
response.setSource(WebCore::ResourceResponse::Source::DiskCache);

Entry updateEntry(existingEntry.key(), response, existingEntry.buffer(), collectVaryingRequestHeaders(originalRequest, response));

auto updateRecord = updateEntry.encodeAsStorageRecord();
auto updateEntry = std::make_unique<Entry>(existingEntry.key(), response, existingEntry.buffer(), collectVaryingRequestHeaders(originalRequest, response));
auto updateRecord = updateEntry->encodeAsStorageRecord();

m_storage->store(updateRecord, { });

if (m_statistics)
m_statistics->recordRevalidationSuccess(webPageID, existingEntry.key(), originalRequest);
m_statistics->recordRevalidationSuccess(frameID.first, existingEntry.key(), originalRequest);

return updateEntry;
}

void Cache::remove(const Key& key)
@@ -84,6 +84,8 @@ enum class UseDecision {
NoDueToDecodeFailure,
};

using GlobalFrameID = std::pair<uint64_t /*webPageID*/, uint64_t /*webFrameID*/>;

class Cache {
WTF_MAKE_NONCOPYABLE(Cache);
friend class WTF::NeverDestroyed<Cache>;
@@ -100,9 +102,9 @@ class Cache {
bool isEnabled() const { return !!m_storage; }

// Completion handler may get called back synchronously on failure.
void retrieve(const WebCore::ResourceRequest&, uint64_t webPageID, uint64_t webFrameID, std::function<void (std::unique_ptr<Entry>)>);
void store(const WebCore::ResourceRequest&, const WebCore::ResourceResponse&, RefPtr<WebCore::SharedBuffer>&&, std::function<void (MappedBody&)>);
void update(const WebCore::ResourceRequest&, uint64_t webPageID, const Entry&, const WebCore::ResourceResponse& validatingResponse);
void retrieve(const WebCore::ResourceRequest&, const GlobalFrameID&, std::function<void (std::unique_ptr<Entry>)>);
std::unique_ptr<Entry> store(const WebCore::ResourceRequest&, const WebCore::ResourceResponse&, RefPtr<WebCore::SharedBuffer>&&, std::function<void (MappedBody&)>);
std::unique_ptr<Entry> update(const WebCore::ResourceRequest&, const GlobalFrameID&, const Entry&, const WebCore::ResourceResponse& validatingResponse);

void traverse(std::function<void (const Entry*)>&&);
void remove(const Key&);
@@ -49,6 +49,16 @@ Entry::Entry(const Key& key, const WebCore::ResourceResponse& response, RefPtr<W
ASSERT(m_key.type() == "resource");
}

Entry::Entry(const Entry& other)
: m_key(other.m_key)
, m_timeStamp(other.m_timeStamp)
, m_response(other.m_response)
, m_varyingRequestHeaders(other.m_varyingRequestHeaders)
, m_buffer(other.m_buffer)
, m_sourceStorageRecord(other.m_sourceStorageRecord)
{
}

Entry::Entry(const Storage::Record& storageEntry)
: m_key(storageEntry.key)
, m_timeStamp(storageEntry.timeStamp)
@@ -43,10 +43,11 @@ namespace WebKit {
namespace NetworkCache {

class Entry {
WTF_MAKE_NONCOPYABLE(Entry); WTF_MAKE_FAST_ALLOCATED;
WTF_MAKE_FAST_ALLOCATED;
public:
Entry(const Key&, const WebCore::ResourceResponse&, RefPtr<WebCore::SharedBuffer>&&, const Vector<std::pair<String, String>>& varyingRequestHeaders);
explicit Entry(const Storage::Record&);
Entry(const Entry&);

Storage::Record encodeAsStorageRecord() const;
static std::unique_ptr<Entry> decodeStorageRecord(const Storage::Record&);

0 comments on commit 5673c2e

Please sign in to comment.