ResourceLoadScheduler.cpp [plain text]
#include "config.h"
#include "ResourceLoadScheduler.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "InspectorInstrumentation.h"
#include "KURL.h"
#include "Logging.h"
#include "NetscapePlugInStreamLoader.h"
#include "ResourceLoader.h"
#include "ResourceRequest.h"
#include "SubresourceLoader.h"
#include <wtf/MainThread.h>
#include <wtf/text/CString.h>
#define REQUEST_MANAGEMENT_ENABLED 1
namespace WebCore {
#if REQUEST_MANAGEMENT_ENABLED
static unsigned maxRequestsInFlightPerHost;
static const unsigned maxRequestsInFlightForNonHTTPProtocols = 10000;
#else
static const unsigned maxRequestsInFlightForNonHTTPProtocols = 10000;
static const unsigned maxRequestsInFlightPerHost = 10000;
#endif
ResourceLoadScheduler::HostInformation* ResourceLoadScheduler::hostForURL(const KURL& url, CreateHostPolicy createHostPolicy)
{
if (!url.protocolIsInHTTPFamily())
return m_nonHTTPProtocolHost;
m_hosts.checkConsistency();
String hostName = url.host();
HostInformation* host = m_hosts.get(hostName);
if (!host && createHostPolicy == CreateIfNotFound) {
host = new HostInformation(hostName, maxRequestsInFlightPerHost);
m_hosts.add(hostName, host);
}
return host;
}
ResourceLoadScheduler* resourceLoadScheduler()
{
ASSERT(isMainThread() || pthread_main_np());
DEFINE_STATIC_LOCAL(ResourceLoadScheduler, resourceLoadScheduler, ());
return &resourceLoadScheduler;
}
ResourceLoadScheduler::ResourceLoadScheduler()
: m_nonHTTPProtocolHost(new HostInformation(String(), maxRequestsInFlightForNonHTTPProtocols))
, m_requestTimer(this, &ResourceLoadScheduler::requestTimerFired)
, m_suspendPendingRequestsCount(0)
, m_isSerialLoadingEnabled(false)
{
#if REQUEST_MANAGEMENT_ENABLED
maxRequestsInFlightPerHost = initializeMaximumHTTPConnectionCountPerHost();
#endif
}
PassRefPtr<SubresourceLoader> ResourceLoadScheduler::scheduleSubresourceLoad(Frame* frame, CachedResource* resource, const ResourceRequest& request, ResourceLoadPriority priority, const ResourceLoaderOptions& options)
{
RefPtr<SubresourceLoader> loader = SubresourceLoader::create(frame, resource, request, options);
if (loader)
scheduleLoad(loader.get(), priority);
if (!loader || loader->reachedTerminalState())
return 0;
return loader.release();
}
PassRefPtr<NetscapePlugInStreamLoader> ResourceLoadScheduler::schedulePluginStreamLoad(Frame* frame, NetscapePlugInStreamLoaderClient* client, const ResourceRequest& request)
{
PassRefPtr<NetscapePlugInStreamLoader> loader = NetscapePlugInStreamLoader::create(frame, client, request);
if (loader)
scheduleLoad(loader.get(), ResourceLoadPriorityLow);
return loader;
}
void ResourceLoadScheduler::addMainResourceLoad(ResourceLoader* resourceLoader)
{
hostForURL(resourceLoader->url(), CreateIfNotFound)->addLoadInProgress(resourceLoader);
}
void ResourceLoadScheduler::scheduleLoad(ResourceLoader* resourceLoader, ResourceLoadPriority priority)
{
ASSERT(resourceLoader);
ASSERT(priority != ResourceLoadPriorityUnresolved);
#if !REQUEST_MANAGEMENT_ENABLED
priority = ResourceLoadPriorityHighest;
#endif
LOG(ResourceLoading, "ResourceLoadScheduler::load resource %p '%s'", resourceLoader, resourceLoader->url().string().latin1().data());
if (!isSuspendingPendingRequests() && resourceLoader->documentLoader()->archiveResourceForURL(resourceLoader->iOSOriginalRequest().url())) {
resourceLoader->startLoading();
return;
}
HostInformation* host = hostForURL(resourceLoader->iOSOriginalRequest().url(), CreateIfNotFound);
bool hadRequests = host->hasRequests();
host->schedule(resourceLoader, priority);
if (ResourceRequest::httpPipeliningEnabled() && !isSuspendingPendingRequests()) {
servePendingRequests(host, ResourceLoadPriorityVeryLow);
return;
}
if ((priority > ResourceLoadPriorityLow || !resourceLoader->iOSOriginalRequest().url().protocolIsInHTTPFamily() || (priority == ResourceLoadPriorityLow && !hadRequests)) && !isSuspendingPendingRequests()) {
servePendingRequests(host, priority);
return;
}
InspectorInstrumentation::didScheduleResourceRequest(resourceLoader->frameLoader() ? resourceLoader->frameLoader()->frame()->document() : 0, resourceLoader->url());
scheduleServePendingRequests();
}
void ResourceLoadScheduler::remove(ResourceLoader* resourceLoader)
{
ASSERT(resourceLoader);
HostInformation* host = hostForURL(resourceLoader->url());
if (host)
host->remove(resourceLoader);
if (!resourceLoader->iOSOriginalRequest().isNull()) {
HostInformation* originalHost = hostForURL(resourceLoader->iOSOriginalRequest().url());
if (originalHost && originalHost != host)
originalHost->remove(resourceLoader);
}
scheduleServePendingRequests();
}
void ResourceLoadScheduler::crossOriginRedirectReceived(ResourceLoader* resourceLoader, const KURL& redirectURL)
{
HostInformation* oldHost = hostForURL(resourceLoader->url());
if (!oldHost)
return;
ASSERT(oldHost);
HostInformation* newHost = hostForURL(redirectURL, CreateIfNotFound);
if (oldHost->name() == newHost->name())
return;
newHost->addLoadInProgress(resourceLoader);
oldHost->remove(resourceLoader);
}
void ResourceLoadScheduler::servePendingRequests(ResourceLoadPriority minimumPriority)
{
LOG(ResourceLoading, "ResourceLoadScheduler::servePendingRequests. m_suspendPendingRequestsCount=%d", m_suspendPendingRequestsCount);
if (isSuspendingPendingRequests())
return;
m_requestTimer.stop();
servePendingRequests(m_nonHTTPProtocolHost, minimumPriority);
Vector<HostInformation*> hostsToServe;
m_hosts.checkConsistency();
HostMap::iterator end = m_hosts.end();
for (HostMap::iterator iter = m_hosts.begin(); iter != end; ++iter)
hostsToServe.append(iter->second);
int size = hostsToServe.size();
for (int i = 0; i < size; ++i) {
HostInformation* host = hostsToServe[i];
if (host->hasRequests())
servePendingRequests(host, minimumPriority);
else
delete m_hosts.take(host->name());
}
}
void ResourceLoadScheduler::servePendingRequests(HostInformation* host, ResourceLoadPriority minimumPriority)
{
LOG(ResourceLoading, "ResourceLoadScheduler::servePendingRequests HostInformation.m_name='%s'", host->name().latin1().data());
for (int priority = ResourceLoadPriorityHighest; priority >= minimumPriority; --priority) {
HostInformation::RequestQueue& requestsPending = host->requestsPending(ResourceLoadPriority(priority));
while (!requestsPending.isEmpty()) {
RefPtr<ResourceLoader> resourceLoader = requestsPending.first();
Document* document = resourceLoader->frameLoader() ? resourceLoader->frameLoader()->frame()->document() : 0;
bool shouldLimitRequests = !host->name().isNull() || (document && (document->parsing() || !document->haveStylesheetsLoaded()));
if (shouldLimitRequests && host->limitRequests(ResourceLoadPriority(priority)))
return;
requestsPending.removeFirst();
host->addLoadInProgress(resourceLoader.get());
resourceLoader->startLoading();
if (resourceLoader->reachedTerminalState())
resourceLoader->clearCachedResourceAfterSynchronousCancel();
}
}
}
void ResourceLoadScheduler::suspendPendingRequests()
{
++m_suspendPendingRequestsCount;
}
void ResourceLoadScheduler::resumePendingRequests()
{
ASSERT(m_suspendPendingRequestsCount);
--m_suspendPendingRequestsCount;
if (m_suspendPendingRequestsCount)
return;
if (!m_hosts.isEmpty() || m_nonHTTPProtocolHost->hasRequests())
scheduleServePendingRequests();
}
void ResourceLoadScheduler::scheduleServePendingRequests()
{
LOG(ResourceLoading, "ResourceLoadScheduler::scheduleServePendingRequests, m_requestTimer.isActive()=%u", m_requestTimer.isActive());
if (!m_requestTimer.isActive())
m_requestTimer.startOneShot(0);
}
void ResourceLoadScheduler::requestTimerFired(Timer<ResourceLoadScheduler>*)
{
LOG(ResourceLoading, "ResourceLoadScheduler::requestTimerFired\n");
servePendingRequests();
}
ResourceLoadScheduler::HostInformation::HostInformation(const String& name, unsigned maxRequestsInFlight)
: m_name(name)
, m_maxRequestsInFlight(maxRequestsInFlight)
{
}
ResourceLoadScheduler::HostInformation::~HostInformation()
{
ASSERT(m_requestsLoading.isEmpty());
for (unsigned p = 0; p <= ResourceLoadPriorityHighest; p++)
ASSERT(m_requestsPending[p].isEmpty());
}
void ResourceLoadScheduler::HostInformation::schedule(ResourceLoader* resourceLoader, ResourceLoadPriority priority)
{
m_requestsPending[priority].append(resourceLoader);
}
void ResourceLoadScheduler::HostInformation::addLoadInProgress(ResourceLoader* resourceLoader)
{
LOG(ResourceLoading, "HostInformation '%s' loading '%s'. Current count %d", m_name.latin1().data(), resourceLoader->url().string().latin1().data(), m_requestsLoading.size());
m_requestsLoading.add(resourceLoader);
}
void ResourceLoadScheduler::HostInformation::remove(ResourceLoader* resourceLoader)
{
if (m_requestsLoading.contains(resourceLoader)) {
m_requestsLoading.remove(resourceLoader);
return;
}
for (int priority = ResourceLoadPriorityHighest; priority >= ResourceLoadPriorityLowest; --priority) {
RequestQueue::iterator end = m_requestsPending[priority].end();
for (RequestQueue::iterator it = m_requestsPending[priority].begin(); it != end; ++it) {
if (*it == resourceLoader) {
m_requestsPending[priority].remove(it);
return;
}
}
}
}
bool ResourceLoadScheduler::HostInformation::hasRequests() const
{
if (!m_requestsLoading.isEmpty())
return true;
for (unsigned p = 0; p <= ResourceLoadPriorityHighest; p++) {
if (!m_requestsPending[p].isEmpty())
return true;
}
return false;
}
bool ResourceLoadScheduler::HostInformation::limitRequests(ResourceLoadPriority priority) const
{
if (priority == ResourceLoadPriorityVeryLow && !m_requestsLoading.isEmpty())
return true;
return m_requestsLoading.size() >= (resourceLoadScheduler()->isSerialLoadingEnabled() ? 1 : m_maxRequestsInFlight);
}
}