| #include "config.h" |
| #include "NetworkResourceLoadScheduler.h" |
| |
| #include "HostRecord.h" |
| #include "Logging.h" |
| #include "NetworkConnectionToWebProcess.h" |
| #include "NetworkProcessconnectionMessages.h" |
| #include "NetworkResourceLoadParameters.h" |
| #include "NetworkResourceLoader.h" |
| #include <wtf/MainThread.h> |
| #include <wtf/text/CString.h> |
| |
| #if ENABLE(NETWORK_PROCESS) |
| |
| using namespace WebCore; |
| |
| namespace WebKit { |
| |
| static const unsigned maxRequestsInFlightForNonHTTPProtocols = 20; |
| static unsigned maxRequestsInFlightPerHost; |
| static ResourceLoadIdentifier s_currentResourceLoadIdentifier; |
| |
| NetworkResourceLoadScheduler::NetworkResourceLoadScheduler() |
| : m_nonHTTPProtocolHost(new HostRecord(String(), maxRequestsInFlightForNonHTTPProtocols)) |
| , m_requestTimer(this, &NetworkResourceLoadScheduler::requestTimerFired) |
| |
| { |
| maxRequestsInFlightPerHost = platformInitializeMaximumHTTPConnectionCountPerHost(); |
| } |
| |
| void NetworkResourceLoadScheduler::scheduleServePendingRequests() |
| { |
| if (!m_requestTimer.isActive()) |
| m_requestTimer.startOneShot(0); |
| } |
| |
| void NetworkResourceLoadScheduler::requestTimerFired(WebCore::Timer<NetworkResourceLoadScheduler>*) |
| { |
| servePendingRequests(); |
| } |
| |
| ResourceLoadIdentifier NetworkResourceLoadScheduler::scheduleResourceLoad(const NetworkResourceLoadParameters& loadParameters, NetworkConnectionToWebProcess* connection) |
| { |
| ResourceLoadPriority priority = loadParameters.priority(); |
| const ResourceRequest& resourceRequest = loadParameters.request(); |
| |
| ResourceLoadIdentifier identifier = ++s_currentResourceLoadIdentifier; |
| RefPtr<NetworkResourceLoader> loader = NetworkResourceLoader::create(loadParameters, identifier, connection); |
| |
| m_resourceLoaders.add(identifier, loader); |
| |
| LOG(NetworkScheduling, "(NetworkProcess) NetworkResourceLoadScheduler::scheduleNetworkResourceRequest resource %llu '%s'", identifier, resourceRequest.url().string().utf8().data()); |
| |
| HostRecord* host = hostForURL(resourceRequest.url(), CreateIfNotFound); |
| bool hadRequests = host->hasRequests(); |
| host->schedule(loader); |
| m_identifiers.add(identifier, host); |
| |
| if (priority > ResourceLoadPriorityLow || !resourceRequest.url().protocolIsInHTTPFamily() || (priority == ResourceLoadPriorityLow && !hadRequests)) { |
| // Try to request important resources immediately. |
| servePendingRequestsForHost(host, priority); |
| return identifier; |
| } |
| |
| // Handle asynchronously so early low priority requests don't get scheduled before later high priority ones. |
| scheduleServePendingRequests(); |
| return identifier; |
| } |
| |
| ResourceLoadIdentifier NetworkResourceLoadScheduler::addLoadInProgress(const WebCore::KURL& url) |
| { |
| ResourceLoadIdentifier identifier = ++s_currentResourceLoadIdentifier; |
| |
| LOG(NetworkScheduling, "(NetworkProcess) NetworkResourceLoadScheduler::addLoadInProgress resource %llu with url '%s'", identifier, url.string().utf8().data()); |
| |
| HostRecord* host = hostForURL(url, CreateIfNotFound); |
| host->addLoadInProgress(identifier); |
| m_identifiers.add(identifier, host); |
| |
| return identifier; |
| } |
| |
| HostRecord* NetworkResourceLoadScheduler::hostForURL(const WebCore::KURL& url, CreateHostPolicy createHostPolicy) |
| { |
| if (!url.protocolIsInHTTPFamily()) |
| return m_nonHTTPProtocolHost; |
| |
| m_hosts.checkConsistency(); |
| String hostName = url.host(); |
| HostRecord* host = m_hosts.get(hostName); |
| if (!host && createHostPolicy == CreateIfNotFound) { |
| host = new HostRecord(hostName, maxRequestsInFlightPerHost); |
| m_hosts.add(hostName, host); |
| } |
| |
| return host; |
| } |
| |
| void NetworkResourceLoadScheduler::removeLoadIdentifier(ResourceLoadIdentifier identifier) |
| { |
| ASSERT(isMainThread()); |
| ASSERT(identifier); |
| |
| LOG(NetworkScheduling, "(NetworkProcess) NetworkResourceLoadScheduler::removeLoadIdentifier removing load identifier %llu", identifier); |
| |
| HostRecord* host = m_identifiers.take(identifier); |
| |
| // Due to a race condition the WebProcess might have messaged the NetworkProcess to remove this identifier |
| // after the NetworkProcess has already removed it internally. |
| // In this situation we might not have a HostRecord to clean up. |
| if (host) |
| host->remove(identifier); |
| |
| m_resourceLoaders.remove(identifier); |
| |
| scheduleServePendingRequests(); |
| } |
| |
| NetworkResourceLoader* NetworkResourceLoadScheduler::networkResourceLoaderForIdentifier(ResourceLoadIdentifier identifier) |
| { |
| ASSERT(m_resourceLoaders.get(identifier)); |
| return m_resourceLoaders.get(identifier).get(); |
| } |
| |
| void NetworkResourceLoadScheduler::receivedRedirect(ResourceLoadIdentifier identifier, const WebCore::KURL& redirectURL) |
| { |
| ASSERT(isMainThread()); |
| LOG(NetworkScheduling, "(NetworkProcess) NetworkResourceLoadScheduler::receivedRedirect resource %llu redirected to '%s'", identifier, redirectURL.string().utf8().data()); |
| |
| HostRecord* oldHost = m_identifiers.get(identifier); |
| HostRecord* newHost = hostForURL(redirectURL, CreateIfNotFound); |
| ASSERT(oldHost); |
| |
| if (oldHost->name() == newHost->name()) |
| return; |
| |
| newHost->addLoadInProgress(identifier); |
| m_identifiers.set(identifier, newHost); |
| |
| oldHost->remove(identifier); |
| } |
| |
| void NetworkResourceLoadScheduler::servePendingRequests(ResourceLoadPriority minimumPriority) |
| { |
| if (m_suspendPendingRequestsCount) |
| return; |
| |
| LOG(NetworkScheduling, "(NetworkProcess) NetworkResourceLoadScheduler::servePendingRequests Serving requests for up to %i hosts", m_hosts.size()); |
| |
| m_requestTimer.stop(); |
| |
| servePendingRequestsForHost(m_nonHTTPProtocolHost, minimumPriority); |
| |
| m_hosts.checkConsistency(); |
| Vector<HostRecord*> hostsToServe; |
| copyValuesToVector(m_hosts, hostsToServe); |
| |
| size_t size = hostsToServe.size(); |
| for (size_t i = 0; i < size; ++i) { |
| HostRecord* host = hostsToServe[i]; |
| if (host->hasRequests()) |
| servePendingRequestsForHost(host, minimumPriority); |
| else |
| delete m_hosts.take(host->name()); |
| } |
| } |
| |
| void NetworkResourceLoadScheduler::servePendingRequestsForHost(HostRecord* host, ResourceLoadPriority minimumPriority) |
| { |
| LOG(NetworkScheduling, "NetworkResourceLoadScheduler::servePendingRequests Host name='%s'", host->name().utf8().data()); |
| |
| for (int priority = ResourceLoadPriorityHighest; priority >= minimumPriority; --priority) { |
| HostRecord::LoaderQueue& loadersPending = host->loadersPending(ResourceLoadPriority(priority)); |
| |
| while (!loadersPending.isEmpty()) { |
| RefPtr<NetworkResourceLoader> loader = loadersPending.first(); |
| |
| // This request might be from WebProcess we've lost our connection to. |
| // If so we should just skip it. |
| if (!loader->connectionToWebProcess()) { |
| loadersPending.removeFirst(); |
| continue; |
| } |
| |
| // For named hosts - which are only http(s) hosts - we should always enforce the connection limit. |
| // For non-named hosts - everything but http(s) - we should only enforce the limit if the document |
| // isn't done parsing and we don't know all stylesheets yet. |
| |
| // FIXME (NetworkProcess): The above comment about document parsing and stylesheets is a holdover |
| // from the WebCore::ResourceLoadScheduler. |
| // The behavior described was at one time important for WebCore's single threadedness. |
| // It's possible that we don't care about it with the NetworkProcess. |
| // We should either decide it's not important and change the above comment, or decide it is |
| // still important and somehow account for it. |
| |
| bool shouldLimitRequests = !host->name().isNull(); |
| if (shouldLimitRequests && host->limitRequests(ResourceLoadPriority(priority), loader->connectionToWebProcess()->isSerialLoadingEnabled())) |
| return; |
| |
| loadersPending.removeFirst(); |
| host->addLoadInProgress(loader->identifier()); |
| |
| loader->start(); |
| } |
| } |
| } |
| |
| void NetworkResourceLoadScheduler::suspendPendingRequests() |
| { |
| ++m_suspendPendingRequestsCount; |
| } |
| |
| void NetworkResourceLoadScheduler::resumePendingRequests() |
| { |
| ASSERT(m_suspendPendingRequestsCount); |
| --m_suspendPendingRequestsCount; |
| if (m_suspendPendingRequestsCount) |
| return; |
| |
| if (!m_hosts.isEmpty() || m_nonHTTPProtocolHost->hasRequests()) |
| scheduleServePendingRequests(); |
| } |
| |
| static bool removeScheduledLoadIdentifiersCalled = false; |
| |
| void NetworkResourceLoadScheduler::removeScheduledLoadIdentifiers(void* context) |
| { |
| ASSERT(isMainThread()); |
| ASSERT(removeScheduledLoadIdentifiersCalled); |
| |
| NetworkResourceLoadScheduler* scheduler = static_cast<NetworkResourceLoadScheduler*>(context); |
| scheduler->removeScheduledLoadIdentifiers(); |
| } |
| |
| void NetworkResourceLoadScheduler::removeScheduledLoadIdentifiers() |
| { |
| Vector<ResourceLoadIdentifier> identifiers; |
| { |
| MutexLocker locker(m_identifiersToRemoveMutex); |
| copyToVector(m_identifiersToRemove, identifiers); |
| m_identifiersToRemove.clear(); |
| removeScheduledLoadIdentifiersCalled = false; |
| } |
| |
| for (size_t i = 0; i < identifiers.size(); ++i) |
| removeLoadIdentifier(identifiers[i]); |
| } |
| |
| void NetworkResourceLoadScheduler::scheduleRemoveLoadIdentifier(ResourceLoadIdentifier identifier) |
| { |
| MutexLocker locker(m_identifiersToRemoveMutex); |
| |
| m_identifiersToRemove.add(identifier); |
| |
| if (!removeScheduledLoadIdentifiersCalled) { |
| removeScheduledLoadIdentifiersCalled = true; |
| callOnMainThread(NetworkResourceLoadScheduler::removeScheduledLoadIdentifiers, this); |
| } |
| } |
| |
| } // namespace WebKit |
| |
| #endif // ENABLE(NETWORK_PROCESS) |