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

Changeset 233111 in webkit


Ignore:
Timestamp:
Jun 22, 2018, 5:09:37 PM (8 years ago)
Author:
Chris Dumez
Message:

Implement IPC throttling to keep the main thread responsive when a process misbehaves
​https://bugs.webkit.org/show_bug.cgi?id=186607
<rdar://problem/41073205>

Reviewed by Geoff Garen and Brady Eidson.

Implement IPC throttling to keep the main thread responsive when a process misbehaves.
Instead of doing one main runloop dispatch per incoming message, we now do a single
runloop dispatch and process incoming messages in batch. We put a limit on the number
of messages to be processed in a batch (600). If the queue is larger that this limit,
we'll schedule a 0-timer to process remaining messages, giving the main runloop a chance
to process other events. Additionally, if an IPC connection keeps hitting this maximum
batch size limit, we implement back off and we'll further decrease the number of messages
we process in each batch (going as low as 60). This keeps Safari responsive enough to
allow the user to close the bad tab (even on older devices such as iPhone 5s).

Finally, if the incoming message queue becomes too large (50000), we go one step further
and kill the IPC connection in order to maintain performance / battery life.

Every time we apply throttling or terminate a connection due to throttling, we do a
RELEASE_LOG_ERROR() with useful information in order to help diagnose potential issues
in the future.

For now, incoming IPC messages throttling is only enabled on the UIProcess' connections
to the WebProcesses.

  • Platform/IPC/Connection.cpp:

(IPC::Connection::Connection):
(IPC::Connection::enqueueIncomingMessage):
(IPC::Connection::MessagesThrottler::MessagesThrottler):
(IPC::Connection::MessagesThrottler::scheduleMessagesDispatch):
(IPC::Connection::MessagesThrottler::numberOfMessagesToProcess):
(IPC::Connection::dispatchIncomingMessages):

  • Platform/IPC/Connection.h:
  • Platform/IPC/mac/ConnectionMac.mm:

(IPC::Connection::kill):

Location:
trunk/Source/WebKit
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/ChangeLog

    r233108 r233111  
     12018-06-22  Chris Dumez  <cdumez@apple.com>
     2
     3        Implement IPC throttling to keep the main thread responsive when a process misbehaves
     4        https://bugs.webkit.org/show_bug.cgi?id=186607
     5        <rdar://problem/41073205>
     6
     7        Reviewed by Geoff Garen and Brady Eidson.
     8
     9        Implement IPC throttling to keep the main thread responsive when a process misbehaves.
     10        Instead of doing one main runloop dispatch per incoming message, we now do a single
     11        runloop dispatch and process incoming messages in batch. We put a limit on the number
     12        of messages to be processed in a batch (600). If the queue is larger that this limit,
     13        we'll schedule a 0-timer to process remaining messages, giving the main runloop a chance
     14        to process other events. Additionally, if an IPC connection keeps hitting this maximum
     15        batch size limit, we implement back off and we'll further decrease the number of messages
     16        we process in each batch (going as low as 60). This keeps Safari responsive enough to
     17        allow the user to close the bad tab (even on older devices such as iPhone 5s).
     18
     19        Finally, if the incoming message queue becomes too large (50000), we go one step further
     20        and kill the IPC connection in order to maintain performance / battery life.
     21
     22        Every time we apply throttling or terminate a connection due to throttling, we do a
     23        RELEASE_LOG_ERROR() with useful information in order to help diagnose potential issues
     24        in the future.
     25
     26        For now, incoming IPC messages throttling is only enabled on the UIProcess' connections
     27        to the WebProcesses.
     28
     29        * Platform/IPC/Connection.cpp:
     30        (IPC::Connection::Connection):
     31        (IPC::Connection::enqueueIncomingMessage):
     32        (IPC::Connection::MessagesThrottler::MessagesThrottler):
     33        (IPC::Connection::MessagesThrottler::scheduleMessagesDispatch):
     34        (IPC::Connection::MessagesThrottler::numberOfMessagesToProcess):
     35        (IPC::Connection::dispatchIncomingMessages):
     36        * Platform/IPC/Connection.h:
     37        * Platform/IPC/mac/ConnectionMac.mm:
     38        (IPC::Connection::kill):
     39
    1402018-06-22  Sihui Liu  <sihui_liu@apple.com>
    241
  • trunk/Source/WebKit/Platform/IPC/Connection.cpp

    r233068 r233111  
    4545namespace IPC {
    4646
     47#if PLATFORM(COCOA)
     48// The IPC connection gets killed if the incoming message queue reaches 50000 messages before the main thread has a chance to dispatch them.
     49const size_t maxPendingIncomingMessagesKillingThreshold { 50000 };
     50#endif
     51
    4752struct Connection::ReplyHandler {
    4853    RefPtr<FunctionDispatcher> dispatcher;
    … …  
    755760}
    756761
     762void Connection::enableIncomingMessagesThrottling()
     763{
     764    if (m_incomingMessagesThrottler)
     765        return;
     766
     767    m_incomingMessagesThrottler = std::make_unique<MessagesThrottler>(*this, &Connection::dispatchIncomingMessages);
     768}
     769
    757770void Connection::postConnectionDidCloseOnConnectionWorkQueue()
    758771{
    … …  
    894907    {
    895908        std::lock_guard<Lock> lock(m_incomingMessagesMutex);
     909
     910#if PLATFORM(COCOA)
     911        if (m_wasKilled)
     912            return;
     913
     914        if (m_incomingMessages.size() >= maxPendingIncomingMessagesKillingThreshold) {
     915            if (kill()) {
     916                RELEASE_LOG_ERROR(IPC, "%p - Connection::enqueueIncomingMessage: Over %zu incoming messages have been queued without the main thread processing them, killing the connection as the remote process seems to be misbehaving", this, maxPendingIncomingMessagesKillingThreshold);
     917                m_incomingMessages.clear();
     918            }
     919            return;
     920        }
     921#endif
     922
    896923        m_incomingMessages.append(WTFMove(incomingMessage));
     924
     925        if (m_incomingMessagesThrottler && m_incomingMessages.size() != 1)
     926            return;
    897927    }
    898928
    899929    RunLoop::main().dispatch([protectedThis = makeRef(*this)]() mutable {
    900         protectedThis->dispatchOneMessage();
     930        if (protectedThis->m_incomingMessagesThrottler)
     931            protectedThis->dispatchIncomingMessages();
     932        else
     933            protectedThis->dispatchOneIncomingMessage();
    901934    });
    902935}
    … …  
    950983}
    951984
    952 void Connection::dispatchOneMessage()
     985Connection::MessagesThrottler::MessagesThrottler(Connection& connection, DispatchMessagesFunction dispatchMessages)
     986    : m_dispatchMessagesTimer(RunLoop::main(), &connection, dispatchMessages)
     987    , m_connection(connection)
     988    , m_dispatchMessages(dispatchMessages)
     989{
     990    ASSERT(RunLoop::isMain());
     991}
     992
     993void Connection::MessagesThrottler::scheduleMessagesDispatch()
     994{
     995    ASSERT(RunLoop::isMain());
     996
     997    if (m_throttlingLevel) {
     998        m_dispatchMessagesTimer.startOneShot(0_s);
     999        return;
     1000    }
     1001    RunLoop::main().dispatch([this, protectedConnection = makeRefPtr(&m_connection)]() mutable {
     1002        (protectedConnection.get()->*m_dispatchMessages)();
     1003    });
     1004}
     1005
     1006size_t Connection::MessagesThrottler::numberOfMessagesToProcess(size_t totalMessages)
     1007{
     1008    ASSERT(RunLoop::isMain());
     1009
     1010    // Never dispatch more than 600 messages without returning to the run loop, we can go as low as 60 with maximum throttling level.
     1011    static const size_t maxIncomingMessagesDispatchingBatchSize { 600 };
     1012    static const unsigned maxThrottlingLevel = 9;
     1013
     1014    size_t batchSize = maxIncomingMessagesDispatchingBatchSize / (m_throttlingLevel + 1);
     1015
     1016    if (totalMessages > maxIncomingMessagesDispatchingBatchSize)
     1017        m_throttlingLevel = std::min(m_throttlingLevel + 1, maxThrottlingLevel);
     1018    else if (m_throttlingLevel)
     1019        --m_throttlingLevel;
     1020
     1021    return std::min(totalMessages, batchSize);
     1022}
     1023
     1024void Connection::dispatchOneIncomingMessage()
    9531025{
    9541026    std::unique_ptr<Decoder> message;
    955 
    9561027    {
    9571028        std::lock_guard<Lock> lock(m_incomingMessagesMutex);
    … …  
    9651036}
    9661037
     1038void Connection::dispatchIncomingMessages()
     1039{
     1040    ASSERT(RunLoop::isMain());
     1041
     1042    std::unique_ptr<Decoder> message;
     1043
     1044    size_t messagesToProcess = 0;
     1045    {
     1046        std::lock_guard<Lock> lock(m_incomingMessagesMutex);
     1047        if (m_incomingMessages.isEmpty())
     1048            return;
     1049
     1050        message = m_incomingMessages.takeFirst();
     1051
     1052        // Incoming messages may get adding to the queue by the IPC thread while we're dispatching the messages below.
     1053        // To make sure dispatchIncomingMessages() yields, we only ever process messages that were in the queue when
     1054        // dispatchIncomingMessages() was called. Additionally, the MessageThrottler may further cap the number of
     1055        // messages to process to make sure we give the main run loop a chance to process other events.
     1056        messagesToProcess = m_incomingMessagesThrottler->numberOfMessagesToProcess(m_incomingMessages.size());
     1057        if (messagesToProcess < m_incomingMessages.size()) {
     1058            RELEASE_LOG_ERROR(IPC, "%p - Connection::dispatchIncomingMessages: IPC throttling was triggered (has %zu pending incoming messages, will only process %zu before yielding)", this, m_incomingMessages.size(), messagesToProcess);
     1059#if PLATFORM(COCOA)
     1060            RELEASE_LOG_ERROR(IPC, "%p - Connection::dispatchIncomingMessages: first IPC message in queue is %{public}s::%{public}s", this, message->messageReceiverName().toString().data(), message->messageName().toString().data());
     1061#endif
     1062        }
     1063
     1064        // Re-schedule ourselves *before* we dispatch the messages because we want to process follow-up messages if the client
     1065        // spins a nested run loop while we're dispatching a message. Note that this means we can re-enter this method.
     1066        if (!m_incomingMessages.isEmpty())
     1067            m_incomingMessagesThrottler->scheduleMessagesDispatch();
     1068    }
     1069
     1070    dispatchMessage(WTFMove(message));
     1071
     1072    for (size_t i = 1; i < messagesToProcess; ++i) {
     1073        {
     1074            std::lock_guard<Lock> lock(m_incomingMessagesMutex);
     1075            if (m_incomingMessages.isEmpty())
     1076                return;
     1077
     1078            message = m_incomingMessages.takeFirst();
     1079        }
     1080        dispatchMessage(WTFMove(message));
     1081    }
     1082}
     1083
    9671084void Connection::wakeUpRunLoop()
    9681085{
  • trunk/Source/WebKit/Platform/IPC/Connection.h

    r233068 r233111  
    4141#include <wtf/Lock.h>
    4242#include <wtf/OptionSet.h>
     43#include <wtf/RunLoop.h>
    4344#include <wtf/WorkQueue.h>
    4445#include <wtf/text/CString.h>
    … …  
    210211    void ignoreTimeoutsForTesting() { m_ignoreTimeoutsForTesting = true; }
    211212
     213    void enableIncomingMessagesThrottling();
     214
    212215private:
    213216    Connection(Identifier, bool isServer, Client&);
    … …  
    232235   
    233236    // Called on the listener thread.
    234     void dispatchOneMessage();
     237    void dispatchOneIncomingMessage();
     238    void dispatchIncomingMessages();
    235239    void dispatchMessage(std::unique_ptr<Decoder>);
    236240    void dispatchMessage(Decoder&);
    … …  
    241245    // Can be called on any thread.
    242246    void enqueueIncomingMessage(std::unique_ptr<Decoder>);
     247    size_t incomingMessagesDispatchingBatchSize() const;
    243248
    244249    void willSendSyncMessage(OptionSet<SendSyncOption>);
    … …  
    250255    bool sendMessage(std::unique_ptr<MachMessage>);
    251256#endif
     257
     258    class MessagesThrottler {
     259    public:
     260        typedef void (Connection::*DispatchMessagesFunction)();
     261        MessagesThrottler(Connection&, DispatchMessagesFunction);
     262
     263        size_t numberOfMessagesToProcess(size_t totalMessages);
     264        void scheduleMessagesDispatch();
     265
     266    private:
     267        RunLoop::Timer<Connection> m_dispatchMessagesTimer;
     268        Connection& m_connection;
     269        DispatchMessagesFunction m_dispatchMessages;
     270        unsigned m_throttlingLevel { 0 };
     271    };
    252272
    253273    Client& m_client;
    … …  
    276296    Lock m_incomingMessagesMutex;
    277297    Deque<std::unique_ptr<Decoder>> m_incomingMessages;
     298    std::unique_ptr<MessagesThrottler> m_incomingMessagesThrottler;
    278299
    279300    // Outgoing messages.
    … …  
    340361
    341362    OSObjectPtr<xpc_connection_t> m_xpcConnection;
     363    bool m_wasKilled { false };
    342364#elif OS(WINDOWS)
    343365    // Called on the connection queue.
  • trunk/Source/WebKit/Platform/IPC/mac/ConnectionMac.mm

    r233068 r233111  
    624624    if (m_xpcConnection) {
    625625        xpc_connection_kill(m_xpcConnection.get(), SIGKILL);
     626        m_wasKilled = true;
    626627        return true;
    627628    }
  • trunk/Source/WebKit/UIProcess/WebProcessProxy.cpp

    r232815 r233111  
    186186    ASSERT(this->connection() == &connection);
    187187
     188    // Throttling IPC messages coming from the WebProcesses so that the UIProcess stays responsive, even
     189    // if one of the WebProcesses misbehaves.
     190    connection.enableIncomingMessagesThrottling();
     191
    188192#if ENABLE(SEC_ITEM_SHIM)
    189193    SecItemShimProxy::singleton().initializeConnection(connection);
Note: See TracChangeset for help on using the changeset viewer.