⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 282252 in webkit


Ignore:
Timestamp:
Sep 9, 2021, 5:28:37 PM (5 years ago)
Author:
Ben Nham
Message:

Preconnected socket is sometimes not used for initial request
​https://bugs.webkit.org/show_bug.cgi?id=229686

Reviewed by Alex Christensen.

In the previous release, we preconnected to the main resource origin early in page load from
UIProcess to save ~10-15 ms per page load (​https://bugs.webkit.org/show_bug.cgi?id=204992).
The savings comes from the fact that there's some latency in initiating the request in
WebProcess and waiting for the nav delegate reply in UIProcess. We hide that latency by
having the eventual main resource request reuse the early preconnected socket.

However, this optimization doesn't work for connections to known HTTP/1.1 origins due to
CFNetwork's in-memory cache of known HTTP/1.1 origins. If an origin is in this cache, and
a preconnect is still in flight, then the network stack will go wide immediately and issue
the main resource request on a new socket rather than trying to wait for the preconnect to
finish and reuse the preconnected socket. Thus the preconneted socket just goes to waste.
This can also cause issues with single-threaded web servers that serve only one request at
a time, as the main resource request can get head-of-line blocked by the preconnect that
is wasted.

To work around this, this patch tracks a small number of known HTTP/1.1 origins in memory.
If there is a pending preconnect to an HTTP/1.1 origin and a main resource request to that
origin comes in, then we block the request from going out until the preconnect finishes.
This allows the main resource request to utilize the preconnected socket.

This is worth ~1.25%-2.5% on PLT5 depending on device.

  • NetworkProcess/NetworkLoad.cpp:

(WebKit::NetworkLoad::didCompleteWithError):

  • NetworkProcess/NetworkLoadScheduler.cpp:

(WebKit::NetworkLoadScheduler::schedule):
(WebKit::NetworkLoadScheduler::unschedule):
(WebKit::NetworkLoadScheduler::scheduleLoad):
(WebKit::NetworkLoadScheduler::unscheduleLoad):
(WebKit::NetworkLoadScheduler::scheduleMainResourceLoad):
(WebKit::NetworkLoadScheduler::unscheduleMainResourceLoad):
(WebKit::NetworkLoadScheduler::startedPreconnectForMainResource):
(WebKit::NetworkLoadScheduler::finishedPreconnectForMainResource):
(WebKit::NetworkLoadScheduler::maybePrunePreconnectInfo):
(WebKit::NetworkLoadScheduler::isOriginHTTP1X):
(WebKit::NetworkLoadScheduler::updateOriginProtocolInfo):

  • NetworkProcess/NetworkLoadScheduler.h:
  • NetworkProcess/NetworkProcess.cpp:

(WebKit::NetworkProcess::preconnectTo):

  • NetworkProcess/PreconnectTask.cpp:

(WebKit::PreconnectTask::PreconnectTask):
(WebKit::PreconnectTask::setTimeout):
(WebKit::PreconnectTask::start):

  • NetworkProcess/PreconnectTask.h:
Location:
trunk/Source/WebKit
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/ChangeLog

    r282247 r282252  
     12021-09-09  Ben Nham  <nham@apple.com>
     2
     3        Preconnected socket is sometimes not used for initial request
     4        https://bugs.webkit.org/show_bug.cgi?id=229686
     5
     6        Reviewed by Alex Christensen.
     7
     8        In the previous release, we preconnected to the main resource origin early in page load from
     9        UIProcess to save ~10-15 ms per page load (https://bugs.webkit.org/show_bug.cgi?id=204992).
     10        The savings comes from the fact that there's some latency in initiating the request in
     11        WebProcess and waiting for the nav delegate reply in UIProcess. We hide that latency by
     12        having the eventual main resource request reuse the early preconnected socket.
     13
     14        However, this optimization doesn't work for connections to known HTTP/1.1 origins due to
     15        CFNetwork's in-memory cache of known HTTP/1.1 origins. If an origin is in this cache, and
     16        a preconnect is still in flight, then the network stack will go wide immediately and issue
     17        the main resource request on a new socket rather than trying to wait for the preconnect to
     18        finish and reuse the preconnected socket. Thus the preconneted socket just goes to waste.
     19        This can also cause issues with single-threaded web servers that serve only one request at
     20        a time, as the main resource request can get head-of-line blocked by the preconnect that
     21        is wasted.
     22
     23        To work around this, this patch tracks a small number of known HTTP/1.1 origins in memory.
     24        If there is a pending preconnect to an HTTP/1.1 origin and a main resource request to that
     25        origin comes in, then we block the request from going out until the preconnect finishes.
     26        This allows the main resource request to utilize the preconnected socket.
     27
     28        This is worth ~1.25%-2.5% on PLT5 depending on device.
     29
     30        * NetworkProcess/NetworkLoad.cpp:
     31        (WebKit::NetworkLoad::didCompleteWithError):
     32        * NetworkProcess/NetworkLoadScheduler.cpp:
     33        (WebKit::NetworkLoadScheduler::schedule):
     34        (WebKit::NetworkLoadScheduler::unschedule):
     35        (WebKit::NetworkLoadScheduler::scheduleLoad):
     36        (WebKit::NetworkLoadScheduler::unscheduleLoad):
     37        (WebKit::NetworkLoadScheduler::scheduleMainResourceLoad):
     38        (WebKit::NetworkLoadScheduler::unscheduleMainResourceLoad):
     39        (WebKit::NetworkLoadScheduler::startedPreconnectForMainResource):
     40        (WebKit::NetworkLoadScheduler::finishedPreconnectForMainResource):
     41        (WebKit::NetworkLoadScheduler::maybePrunePreconnectInfo):
     42        (WebKit::NetworkLoadScheduler::isOriginHTTP1X):
     43        (WebKit::NetworkLoadScheduler::updateOriginProtocolInfo):
     44        * NetworkProcess/NetworkLoadScheduler.h:
     45        * NetworkProcess/NetworkProcess.cpp:
     46        (WebKit::NetworkProcess::preconnectTo):
     47        * NetworkProcess/PreconnectTask.cpp:
     48        (WebKit::PreconnectTask::PreconnectTask):
     49        (WebKit::PreconnectTask::setTimeout):
     50        (WebKit::PreconnectTask::start):
     51        * NetworkProcess/PreconnectTask.h:
     52
    1532021-09-09  Wenson Hsieh  <wenson_hsieh@apple.com>
    254
  • trunk/Source/WebKit/NetworkProcess/NetworkLoad.cpp

    r276880 r282252  
    246246{
    247247    if (m_scheduler) {
    248         m_scheduler->unschedule(*this);
     248        m_scheduler->unschedule(*this, &networkLoadMetrics);
    249249        m_scheduler = nullptr;
    250250    }
  • trunk/Source/WebKit/NetworkProcess/NetworkLoadScheduler.cpp

    r274707 r282252  
    2727#include "NetworkLoadScheduler.h"
    2828
     29#include "Logging.h"
    2930#include "NetworkLoad.h"
    30 #include <wtf/ListHashSet.h>
     31#include <WebCore/ResourceError.h>
    3132
    3233namespace WebKit {
    3334
    3435static constexpr size_t maximumActiveCountForLowPriority = 2;
     36static constexpr size_t maximumTrackedHTTP1XOrigins = 128;
    3537
    3638class NetworkLoadScheduler::HostContext {
    … …  
    118120void NetworkLoadScheduler::schedule(NetworkLoad& load)
    119121{
     122    bool isMainFrameMainResource = load.currentRequest().isTopSite();
     123    if (isMainFrameMainResource)
     124        scheduleMainResourceLoad(load);
     125    else
     126        scheduleLoad(load);
     127}
     128
     129void NetworkLoadScheduler::unschedule(NetworkLoad& load, const WebCore::NetworkLoadMetrics* metrics)
     130{
     131    bool isMainFrameMainResource = load.currentRequest().isTopSite();
     132    if (isMainFrameMainResource)
     133        unscheduleMainResourceLoad(load, metrics);
     134    else
     135        unscheduleLoad(load);
     136}
     137
     138void NetworkLoadScheduler::scheduleLoad(NetworkLoad& load)
     139{
    120140    auto* context = contextForLoad(load);
    121141
    … …  
    128148}
    129149
    130 void NetworkLoadScheduler::unschedule(NetworkLoad& load)
     150void NetworkLoadScheduler::unscheduleLoad(NetworkLoad& load)
    131151{
    132152    if (auto* context = contextForLoad(load))
    133153        context->unschedule(load);
     154}
     155
     156void NetworkLoadScheduler::scheduleMainResourceLoad(NetworkLoad& load)
     157{
     158    String protocolHostAndPort = load.url().protocolHostAndPort();
     159    if (!isOriginHTTP1X(protocolHostAndPort)) {
     160        load.start();
     161        return;
     162    }
     163
     164    auto iter = m_pendingMainResourcePreconnects.find(protocolHostAndPort);
     165    if (iter == m_pendingMainResourcePreconnects.end()) {
     166        load.start();
     167        return;
     168    }
     169
     170    PendingMainResourcePreconnectInfo& info = iter->value;
     171    if (!info.pendingPreconnects) {
     172        load.start();
     173        return;
     174    }
     175
     176    --info.pendingPreconnects;
     177    info.pendingLoads.add(&load);
     178    RELEASE_LOG(Network, "%p - NetworkLoadScheduler::scheduleMainResourceLoad deferring load %p; %u pending preconnects; %u pending loads", this, &load, info.pendingPreconnects, info.pendingLoads.size());
     179}
     180
     181void NetworkLoadScheduler::unscheduleMainResourceLoad(NetworkLoad& load, const WebCore::NetworkLoadMetrics* metrics)
     182{
     183    String protocolHostAndPort = load.url().protocolHostAndPort();
     184
     185    if (metrics)
     186        updateOriginProtocolInfo(protocolHostAndPort, metrics->protocol);
     187
     188    auto iter = m_pendingMainResourcePreconnects.find(protocolHostAndPort);
     189    if (iter == m_pendingMainResourcePreconnects.end())
     190        return;
     191
     192    PendingMainResourcePreconnectInfo& info = iter->value;
     193    if (info.pendingLoads.remove(&load))
     194        maybePrunePreconnectInfo(iter);
     195}
     196
     197void NetworkLoadScheduler::startedPreconnectForMainResource(const URL& url)
     198{
     199    auto iter = m_pendingMainResourcePreconnects.find(url.protocolHostAndPort());
     200    if (iter != m_pendingMainResourcePreconnects.end()) {
     201        PendingMainResourcePreconnectInfo& info = iter->value;
     202        info.pendingPreconnects++;
     203        return;
     204    }
     205
     206    PendingMainResourcePreconnectInfo info;
     207    m_pendingMainResourcePreconnects.add(url.protocolHostAndPort(), WTFMove(info));
     208}
     209
     210void NetworkLoadScheduler::finishedPreconnectForMainResource(const URL& url, const WebCore::ResourceError& error)
     211{
     212    auto iter = m_pendingMainResourcePreconnects.find(url.protocolHostAndPort());
     213    if (iter == m_pendingMainResourcePreconnects.end())
     214        return;
     215
     216    PendingMainResourcePreconnectInfo& info = iter->value;
     217    if (!info.pendingLoads.isEmpty()) {
     218        NetworkLoad* load = info.pendingLoads.takeFirst();
     219        RELEASE_LOG(Network, "%p - NetworkLoadScheduler::finishedPreconnectForMainResource (error: %d) starting delayed main resource load %p; %u pending preconnects; %u total pending loads", this, static_cast<int>(error.type()), load, info.pendingPreconnects, info.pendingLoads.size());
     220        load->start();
     221    } else
     222        --info.pendingPreconnects;
     223
     224    maybePrunePreconnectInfo(iter);
     225}
     226
     227void NetworkLoadScheduler::maybePrunePreconnectInfo(PendingPreconnectMap::iterator& iter)
     228{
     229    PendingMainResourcePreconnectInfo& info = iter->value;
     230    if (!info.pendingPreconnects && info.pendingLoads.isEmpty())
     231        m_pendingMainResourcePreconnects.remove(iter);
     232}
     233
     234
     235bool NetworkLoadScheduler::isOriginHTTP1X(const String& protocolHostAndPort)
     236{
     237    return m_http1XOrigins.contains(protocolHostAndPort);
     238}
     239
     240void NetworkLoadScheduler::updateOriginProtocolInfo(const String& protocolHostAndPort, const String& alpnProtocolID)
     241{
     242    if (alpnProtocolID != "http/1.1"_s) {
     243        m_http1XOrigins.remove(protocolHostAndPort);
     244        return;
     245    }
     246
     247    if (m_http1XOrigins.size() >= maximumTrackedHTTP1XOrigins)
     248        m_http1XOrigins.remove(m_http1XOrigins.random());
     249
     250    m_http1XOrigins.add(protocolHostAndPort);
    134251}
    135252
  • trunk/Source/WebKit/NetworkProcess/NetworkLoadScheduler.h

    r271946 r282252  
    2727
    2828#include <WebCore/LoadSchedulingMode.h>
     29#include <WebCore/NetworkLoadMetrics.h>
    2930#include <WebCore/PageIdentifier.h>
    3031#include <wtf/HashMap.h>
     32#include <wtf/HashSet.h>
     33#include <wtf/ListHashSet.h>
    3134#include <wtf/WeakPtr.h>
     35
     36namespace WebCore {
     37class ResourceError;
     38}
    3239
    3340namespace WebKit {
    … …  
    4249
    4350    void schedule(NetworkLoad&);
    44     void unschedule(NetworkLoad&);
     51    void unschedule(NetworkLoad&, const WebCore::NetworkLoadMetrics* = nullptr);
     52
     53    void startedPreconnectForMainResource(const URL&);
     54    void finishedPreconnectForMainResource(const URL&, const WebCore::ResourceError&);
    4555
    4656    void setResourceLoadSchedulingMode(WebCore::PageIdentifier, WebCore::LoadSchedulingMode);
    … …  
    4959
    5060private:
     61    void scheduleLoad(NetworkLoad&);
     62    void unscheduleLoad(NetworkLoad&);
     63
     64    void scheduleMainResourceLoad(NetworkLoad&);
     65    void unscheduleMainResourceLoad(NetworkLoad&, const WebCore::NetworkLoadMetrics*);
     66
     67    bool isOriginHTTP1X(const String&);
     68    void updateOriginProtocolInfo(const String&, const String&);
     69
    5170    class HostContext;
    5271    HostContext* contextForLoad(const NetworkLoad&);
    … …  
    5473    using PageContext = HashMap<String, std::unique_ptr<HostContext>>;
    5574    HashMap<WebCore::PageIdentifier, std::unique_ptr<PageContext>> m_pageContexts;
     75
     76    struct PendingMainResourcePreconnectInfo {
     77        unsigned pendingPreconnects {1};
     78        ListHashSet<NetworkLoad *> pendingLoads;
     79    };
     80    using PendingPreconnectMap = HashMap<String, PendingMainResourcePreconnectInfo>;
     81    PendingPreconnectMap m_pendingMainResourcePreconnects;
     82
     83    void maybePrunePreconnectInfo(PendingPreconnectMap::iterator&);
     84
     85    HashSet<String> m_http1XOrigins;
    5686};
    5787
  • trunk/Source/WebKit/NetworkProcess/NetworkProcess.cpp

    r282170 r282252  
    13881388    parameters.request.setIsAppInitiated(lastNavigationWasAppInitiated == LastNavigationWasAppInitiated::Yes);
    13891389    parameters.request.setFirstPartyForCookies(url);
     1390    parameters.request.setPriority(WebCore::ResourceLoadPriority::VeryHigh);
    13901391    parameters.webPageProxyID = webPageProxyID;
    13911392    parameters.webPageID = webPageID;
    … …  
    13991400    parameters.shouldPreconnectOnly = PreconnectOnly::Yes;
    14001401
    1401     (new PreconnectTask(*networkSession, WTFMove(parameters), [](const WebCore::ResourceError&) { }))->start();
     1402    networkSession->networkLoadScheduler().startedPreconnectForMainResource(url);
     1403    auto task = new PreconnectTask(*networkSession, WTFMove(parameters), [networkSession, url](const WebCore::ResourceError& error) {
     1404        networkSession->networkLoadScheduler().finishedPreconnectForMainResource(url, error);
     1405    });
     1406    task->setTimeout(10_s);
     1407    task->start();
    14021408#else
    14031409    UNUSED_PARAM(url);
  • trunk/Source/WebKit/NetworkProcess/PreconnectTask.cpp

    r273183 r282252  
    4242PreconnectTask::PreconnectTask(NetworkSession& networkSession, NetworkLoadParameters&& parameters, CompletionHandler<void(const ResourceError&)>&& completionHandler)
    4343    : m_completionHandler(WTFMove(completionHandler))
     44    , m_timeout(60_s)
    4445    , m_timeoutTimer([this] { didFinish(ResourceError { String(), 0, m_networkLoad->parameters().request.url(), "Preconnection timed out"_s, ResourceError::Type::Timeout }); })
    4546{
    … …  
    4849    ASSERT(parameters.shouldPreconnectOnly == PreconnectOnly::Yes);
    4950    m_networkLoad = makeUnique<NetworkLoad>(*this, nullptr, WTFMove(parameters), networkSession);
    50     m_timeoutTimer.startOneShot(60000_s);
    5151}
    5252
    … …  
    5656}
    5757
     58void PreconnectTask::setTimeout(Seconds timeout)
     59{
     60    m_timeout = timeout;
     61}
     62
    5863void PreconnectTask::start()
    5964{
     65    m_timeoutTimer.startOneShot(m_timeout);
    6066    m_networkLoad->start();
    6167}
  • trunk/Source/WebKit/NetworkProcess/PreconnectTask.h

    r277858 r282252  
    4646
    4747    void setH2PingCallback(const URL&, CompletionHandler<void(Expected<WTF::Seconds, WebCore::ResourceError>&&)>&&);
     48    void setTimeout(Seconds);
    4849    void start();
    4950
    … …  
    6364    std::unique_ptr<NetworkLoad> m_networkLoad;
    6465    CompletionHandler<void(const WebCore::ResourceError&)> m_completionHandler;
     66    Seconds m_timeout;
    6567    WebCore::Timer m_timeoutTimer;
    6668};
Note: See TracChangeset for help on using the changeset viewer.