1/*
2 * Copyright (C) 2012 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "HostRecord.h"
28
29#include "Logging.h"
30#include "NetworkConnectionToWebProcess.h"
31#include "NetworkProcess.h"
32#include "NetworkResourceLoadParameters.h"
33#include "NetworkResourceLoadScheduler.h"
34#include "NetworkResourceLoader.h"
35#include <wtf/MainThread.h>
36
37#if ENABLE(NETWORK_PROCESS)
38
39using namespace WebCore;
40
41namespace WebKit {
42
43HostRecord::HostRecord(const String& name, int maxRequestsInFlight)
44    : m_name(name)
45    , m_maxRequestsInFlight(maxRequestsInFlight)
46{
47}
48
49HostRecord::~HostRecord()
50{
51#ifndef NDEBUG
52    ASSERT(m_loadersInProgress.isEmpty());
53    for (unsigned p = 0; p <= ResourceLoadPriorityHighest; p++)
54        ASSERT(m_loadersPending[p].isEmpty());
55#endif
56}
57
58void HostRecord::scheduleResourceLoader(PassRefPtr<NetworkResourceLoader> loader)
59{
60    ASSERT(RunLoop::isMain());
61
62    loader->setHostRecord(this);
63
64    if (loader->isSynchronous())
65        m_syncLoadersPending.append(loader);
66    else
67        m_loadersPending[loader->priority()].append(loader);
68}
69
70void HostRecord::addLoaderInProgress(NetworkResourceLoader* loader)
71{
72    ASSERT(RunLoop::isMain());
73
74    m_loadersInProgress.add(loader);
75    loader->setHostRecord(this);
76}
77
78inline bool removeLoaderFromQueue(NetworkResourceLoader* loader, LoaderQueue& queue)
79{
80    LoaderQueue::iterator end = queue.end();
81    for (LoaderQueue::iterator it = queue.begin(); it != end; ++it) {
82        if (it->get() == loader) {
83            loader->setHostRecord(0);
84            queue.remove(it);
85            return true;
86        }
87    }
88    return false;
89}
90
91void HostRecord::removeLoader(NetworkResourceLoader* loader)
92{
93    ASSERT(RunLoop::isMain());
94
95    // FIXME (NetworkProcess): Due to IPC race conditions, it's possible this HostRecord will be asked to remove the same loader twice.
96    // It would be nice to know the loader has already been removed and treat it as a no-op.
97
98    NetworkResourceLoaderSet::iterator i = m_loadersInProgress.find(loader);
99    if (i != m_loadersInProgress.end()) {
100        i->get()->setHostRecord(0);
101        m_loadersInProgress.remove(i);
102        return;
103    }
104
105    if (removeLoaderFromQueue(loader, m_syncLoadersPending))
106        return;
107
108    for (int priority = ResourceLoadPriorityHighest; priority >= ResourceLoadPriorityLowest; --priority) {
109        if (removeLoaderFromQueue(loader, m_loadersPending[priority]))
110            return;
111    }
112}
113
114bool HostRecord::hasRequests() const
115{
116    if (!m_loadersInProgress.isEmpty())
117        return true;
118
119    for (unsigned p = 0; p <= ResourceLoadPriorityHighest; p++) {
120        if (!m_loadersPending[p].isEmpty())
121            return true;
122    }
123
124    return false;
125}
126
127uint64_t HostRecord::pendingRequestCount() const
128{
129    uint64_t count = 0;
130
131    for (unsigned p = 0; p <= ResourceLoadPriorityHighest; p++)
132        count += m_loadersPending[p].size();
133
134    return count;
135}
136
137uint64_t HostRecord::activeLoadCount() const
138{
139    return m_loadersInProgress.size();
140}
141
142void HostRecord::servePendingRequestsForQueue(LoaderQueue& queue, ResourceLoadPriority priority)
143{
144    // We only enforce the connection limit for http(s) hosts, which are the only ones with names.
145    bool shouldLimitRequests = !name().isNull();
146
147    // For non-named hosts - everything but http(s) - we should only enforce the limit if the document
148    // isn't done parsing and we don't know all stylesheets yet.
149
150    // FIXME (NetworkProcess): The above comment about document parsing and stylesheets is a holdover
151    // from the WebCore::ResourceLoadScheduler.
152    // The behavior described was at one time important for WebCore's single threadedness.
153    // It's possible that we don't care about it with the NetworkProcess.
154    // We should either decide it's not important and change the above comment, or decide it is
155    // still important and somehow account for it.
156
157    // Very low priority loaders are only handled when no other loaders are in progress.
158    if (shouldLimitRequests && priority == ResourceLoadPriorityVeryLow && !m_loadersInProgress.isEmpty())
159        return;
160
161    while (!queue.isEmpty()) {
162        RefPtr<NetworkResourceLoader> loader = queue.first();
163        ASSERT(loader->hostRecord() == this);
164
165        // This request might be from WebProcess we've lost our connection to.
166        // If so we should just skip it.
167        if (!loader->connectionToWebProcess()) {
168            removeLoader(loader.get());
169            continue;
170        }
171
172        if (shouldLimitRequests && limitsRequests(priority, loader.get()))
173            return;
174
175        m_loadersInProgress.add(loader);
176        queue.removeFirst();
177
178        LOG(NetworkScheduling, "(NetworkProcess) HostRecord::servePendingRequestsForQueue - Starting load of %s\n", loader->request().url().string().utf8().data());
179        loader->start();
180    }
181}
182
183void HostRecord::servePendingRequests(ResourceLoadPriority minimumPriority)
184{
185    LOG(NetworkScheduling, "(NetworkProcess) HostRecord::servePendingRequests Host name='%s'", name().utf8().data());
186
187    // We serve synchronous requests before any other requests to improve responsiveness in any
188    // WebProcess that is waiting on a synchronous load.
189    servePendingRequestsForQueue(m_syncLoadersPending, ResourceLoadPriorityHighest);
190
191    for (int priority = ResourceLoadPriorityHighest; priority >= minimumPriority; --priority)
192        servePendingRequestsForQueue(m_loadersPending[priority], (ResourceLoadPriority)priority);
193}
194
195bool HostRecord::limitsRequests(ResourceLoadPriority priority, NetworkResourceLoader* loader) const
196{
197    ASSERT(loader);
198    ASSERT(loader->connectionToWebProcess());
199
200    if (priority == ResourceLoadPriorityVeryLow && !m_loadersInProgress.isEmpty())
201        return true;
202
203    if (loader->connectionToWebProcess()->isSerialLoadingEnabled() && m_loadersInProgress.size() >= 1)
204        return true;
205
206    // If we're exactly at the limit for requests in flight, and this loader is asynchronous, then we're done serving new requests.
207    // The synchronous loader exception handles the case where a sync XHR is made while 6 other requests are already in flight.
208    if (m_loadersInProgress.size() == m_maxRequestsInFlight && !loader->isSynchronous())
209        return true;
210
211    // If we're already past the limit of the number of loaders in flight, we won't even serve new synchronous requests right now.
212    if (m_loadersInProgress.size() > m_maxRequestsInFlight) {
213#ifndef NDEBUG
214        // If we have more loaders in progress than we should, at least one of them had better be synchronous.
215        NetworkResourceLoaderSet::iterator i = m_loadersInProgress.begin();
216        NetworkResourceLoaderSet::iterator end = m_loadersInProgress.end();
217        for (; i != end; ++i) {
218            if (i->get()->isSynchronous())
219                break;
220        }
221        ASSERT(i != end);
222#endif
223        return true;
224    }
225    return false;
226}
227
228} // namespace WebKit
229
230#endif // ENABLE(NETWORK_PROCESS)
231