Title: [277893] trunk/Source/WebCore
Revision
277893
Author
cdu...@apple.com
Date
2021-05-21 15:17:33 -0700 (Fri, 21 May 2021)

Log Message

Adopt CheckedLock in ScrollingTree and fix threading bug
https://bugs.webkit.org/show_bug.cgi?id=226109

Reviewed by Darin Adler.

Adopt CheckedLock in ScrollingTree and fix threading bug found by Clang Thread Safety
Analysis. In particular, ThreadedScrollingTree::willStartRenderingUpdate() was failing
to grab the lock before updating m_state. m_state definitely get modified from several
threads so synchronization is important.

* page/scrolling/ScrollingTree.cpp:
(WebCore::ScrollingTree::handleWheelEvent):
(WebCore::ScrollingTree::traverseScrollingTree):
(WebCore::ScrollingTree::commitTreeState):
(WebCore::ScrollingTree::applyLayerPositions):
(WebCore::ScrollingTree::applyLayerPositionsInternal):
(WebCore::ScrollingTree::scrollBySimulatingWheelEventForTesting):
* page/scrolling/ScrollingTree.h:
(WebCore::ScrollingTree::WTF_RETURNS_LOCK):
(WebCore::ScrollingTree::WTF_REQUIRES_LOCK):
* page/scrolling/ThreadedScrollingTree.cpp:
(WebCore::ThreadedScrollingTree::ThreadedScrollingTree):
(WebCore::ThreadedScrollingTree::handleWheelEventAfterMainThread):
(WebCore::ThreadedScrollingTree::wheelEventWasProcessedByMainThread):
(WebCore::ThreadedScrollingTree::willSendEventToMainThread):
(WebCore::ThreadedScrollingTree::waitForEventToBeProcessedByMainThread):
(WebCore::ThreadedScrollingTree::invalidate):
(WebCore::ThreadedScrollingTree::willStartRenderingUpdate):
(WebCore::ThreadedScrollingTree::waitForRenderingUpdateCompletionOrTimeout):
(WebCore::ThreadedScrollingTree::didCompleteRenderingUpdate):
(WebCore::ThreadedScrollingTree::scheduleDelayedRenderingUpdateDetectionTimer):
(WebCore::ThreadedScrollingTree::delayedRenderingUpdateDetectionTimerFired):
(WebCore::ThreadedScrollingTree::displayDidRefreshOnScrollingThread):
* page/scrolling/ThreadedScrollingTree.h:
(WebCore::ThreadedScrollingTree::WTF_RETURNS_LOCK):
(WebCore::ThreadedScrollingTree::WTF_GUARDED_BY_LOCK):
* page/scrolling/mac/ScrollingTreeScrollingNodeDelegateMac.mm:
(WebCore::ScrollingTreeScrollingNodeDelegateMac::createTimer):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (277892 => 277893)


--- trunk/Source/WebCore/ChangeLog	2021-05-21 22:14:58 UTC (rev 277892)
+++ trunk/Source/WebCore/ChangeLog	2021-05-21 22:17:33 UTC (rev 277893)
@@ -1,5 +1,46 @@
 2021-05-21  Chris Dumez  <cdu...@apple.com>
 
+        Adopt CheckedLock in ScrollingTree and fix threading bug
+        https://bugs.webkit.org/show_bug.cgi?id=226109
+
+        Reviewed by Darin Adler.
+
+        Adopt CheckedLock in ScrollingTree and fix threading bug found by Clang Thread Safety
+        Analysis. In particular, ThreadedScrollingTree::willStartRenderingUpdate() was failing
+        to grab the lock before updating m_state. m_state definitely get modified from several
+        threads so synchronization is important.
+
+        * page/scrolling/ScrollingTree.cpp:
+        (WebCore::ScrollingTree::handleWheelEvent):
+        (WebCore::ScrollingTree::traverseScrollingTree):
+        (WebCore::ScrollingTree::commitTreeState):
+        (WebCore::ScrollingTree::applyLayerPositions):
+        (WebCore::ScrollingTree::applyLayerPositionsInternal):
+        (WebCore::ScrollingTree::scrollBySimulatingWheelEventForTesting):
+        * page/scrolling/ScrollingTree.h:
+        (WebCore::ScrollingTree::WTF_RETURNS_LOCK):
+        (WebCore::ScrollingTree::WTF_REQUIRES_LOCK):
+        * page/scrolling/ThreadedScrollingTree.cpp:
+        (WebCore::ThreadedScrollingTree::ThreadedScrollingTree):
+        (WebCore::ThreadedScrollingTree::handleWheelEventAfterMainThread):
+        (WebCore::ThreadedScrollingTree::wheelEventWasProcessedByMainThread):
+        (WebCore::ThreadedScrollingTree::willSendEventToMainThread):
+        (WebCore::ThreadedScrollingTree::waitForEventToBeProcessedByMainThread):
+        (WebCore::ThreadedScrollingTree::invalidate):
+        (WebCore::ThreadedScrollingTree::willStartRenderingUpdate):
+        (WebCore::ThreadedScrollingTree::waitForRenderingUpdateCompletionOrTimeout):
+        (WebCore::ThreadedScrollingTree::didCompleteRenderingUpdate):
+        (WebCore::ThreadedScrollingTree::scheduleDelayedRenderingUpdateDetectionTimer):
+        (WebCore::ThreadedScrollingTree::delayedRenderingUpdateDetectionTimerFired):
+        (WebCore::ThreadedScrollingTree::displayDidRefreshOnScrollingThread):
+        * page/scrolling/ThreadedScrollingTree.h:
+        (WebCore::ThreadedScrollingTree::WTF_RETURNS_LOCK):
+        (WebCore::ThreadedScrollingTree::WTF_GUARDED_BY_LOCK):
+        * page/scrolling/mac/ScrollingTreeScrollingNodeDelegateMac.mm:
+        (WebCore::ScrollingTreeScrollingNodeDelegateMac::createTimer):
+
+2021-05-21  Chris Dumez  <cdu...@apple.com>
+
         Adopt CheckedLock in AudioMediaStreamTrackRendererUnit
         https://bugs.webkit.org/show_bug.cgi?id=226095
 

Modified: trunk/Source/WebCore/page/scrolling/ScrollingTree.cpp (277892 => 277893)


--- trunk/Source/WebCore/page/scrolling/ScrollingTree.cpp	2021-05-21 22:14:58 UTC (rev 277892)
+++ trunk/Source/WebCore/page/scrolling/ScrollingTree.cpp	2021-05-21 22:17:33 UTC (rev 277893)
@@ -125,7 +125,7 @@
 {
     LOG_WITH_STREAM(Scrolling, stream << "\nScrollingTree " << this << " handleWheelEvent " << wheelEvent);
 
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
 
     if (isMonitoringWheelEvents())
         receivedWheelEvent(wheelEvent);
@@ -224,7 +224,7 @@
 
 void ScrollingTree::traverseScrollingTree(VisitorFunction&& visitorFunction)
 {
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
     if (!m_rootNode)
         return;
 
@@ -265,7 +265,7 @@
 void ScrollingTree::commitTreeState(std::unique_ptr<ScrollingStateTree>&& scrollingStateTree)
 {
     SetForScope<bool> inCommitTreeState(m_inCommitTreeState, true);
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
 
     bool rootStateNodeChanged = scrollingStateTree->hasNewRootStateNode();
 
@@ -417,7 +417,7 @@
 void ScrollingTree::applyLayerPositions()
 {
     ASSERT(isMainThread());
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
 
     applyLayerPositionsInternal();
 }
@@ -424,7 +424,7 @@
 
 void ScrollingTree::applyLayerPositionsInternal()
 {
-    ASSERT(m_treeMutex.isLocked());
+    ASSERT(m_treeLock.isLocked());
     if (!m_rootNode)
         return;
 
@@ -664,7 +664,7 @@
 
 void ScrollingTree::scrollBySimulatingWheelEventForTesting(ScrollingNodeID nodeID, FloatSize delta)
 {
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
 
     auto* node = nodeForID(nodeID);
     if (!is<ScrollingTreeScrollingNode>(node))

Modified: trunk/Source/WebCore/page/scrolling/ScrollingTree.h (277892 => 277893)


--- trunk/Source/WebCore/page/scrolling/ScrollingTree.h	2021-05-21 22:14:58 UTC (rev 277892)
+++ trunk/Source/WebCore/page/scrolling/ScrollingTree.h	2021-05-21 22:17:33 UTC (rev 277893)
@@ -217,7 +217,7 @@
     virtual void willSendEventToMainThread(const PlatformWheelEvent&) { }
     virtual void waitForEventToBeProcessedByMainThread(const PlatformWheelEvent&) { };
 
-    Lock& treeMutex() { return m_treeMutex; }
+    CheckedLock& treeLock() WTF_RETURNS_LOCK(m_treeLock) { return m_treeLock; }
 
     void windowScreenDidChange(PlatformDisplayID, Optional<FramesPerSecond> nominalFramesPerSecond);
     PlatformDisplayID displayID();
@@ -257,18 +257,18 @@
 
     Optional<FramesPerSecond> nominalFramesPerSecond();
 
-    void applyLayerPositionsInternal();
-    void removeAllNodes();
+    void applyLayerPositionsInternal() WTF_REQUIRES_LOCK(m_treeLock);
+    void removeAllNodes() WTF_REQUIRES_LOCK(m_treeLock);
 
-    Lock m_treeMutex; // Protects the scrolling tree.
+    CheckedLock m_treeLock; // Protects the scrolling tree.
 
 private:
-    void updateTreeFromStateNodeRecursive(const ScrollingStateNode*, struct CommitTreeState&);
-    virtual void propagateSynchronousScrollingReasons(const HashSet<ScrollingNodeID>&) { }
+    void updateTreeFromStateNodeRecursive(const ScrollingStateNode*, struct CommitTreeState&) WTF_REQUIRES_LOCK(m_treeLock);
+    virtual void propagateSynchronousScrollingReasons(const HashSet<ScrollingNodeID>&) WTF_REQUIRES_LOCK(m_treeLock) { }
 
-    void applyLayerPositionsRecursive(ScrollingTreeNode&);
+    void applyLayerPositionsRecursive(ScrollingTreeNode&) WTF_REQUIRES_LOCK(m_treeLock);
     void notifyRelatedNodesRecursive(ScrollingTreeNode&);
-    void traverseScrollingTreeRecursive(ScrollingTreeNode&, const VisitorFunction&);
+    void traverseScrollingTreeRecursive(ScrollingTreeNode&, const VisitorFunction&) WTF_REQUIRES_LOCK(m_treeLock);
 
     WEBCORE_EXPORT virtual RefPtr<ScrollingTreeNode> scrollingNodeForPoint(FloatPoint);
 #if ENABLE(WHEEL_EVENT_REGIONS)

Modified: trunk/Source/WebCore/page/scrolling/ThreadedScrollingTree.cpp (277892 => 277893)


--- trunk/Source/WebCore/page/scrolling/ThreadedScrollingTree.cpp	2021-05-21 22:14:58 UTC (rev 277892)
+++ trunk/Source/WebCore/page/scrolling/ThreadedScrollingTree.cpp	2021-05-21 22:17:33 UTC (rev 277893)
@@ -46,10 +46,10 @@
 
 ThreadedScrollingTree::ThreadedScrollingTree(AsyncScrollingCoordinator& scrollingCoordinator)
     : m_scrollingCoordinator(&scrollingCoordinator)
-{
 #if ENABLE(SMOOTH_SCROLLING)
-    m_scrollAnimatorEnabled = scrollingCoordinator.scrollAnimatorEnabled();
+    , m_scrollAnimatorEnabled(scrollingCoordinator.scrollAnimatorEnabled())
 #endif
+{
 }
 
 ThreadedScrollingTree::~ThreadedScrollingTree()
@@ -70,7 +70,7 @@
 
     LOG_WITH_STREAM(Scrolling, stream << "ThreadedScrollingTree::handleWheelEventAfterMainThread " << wheelEvent << " node " << targetNodeID << " gestureState " << gestureState);
 
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
 
     bool allowLatching = false;
     OptionSet<WheelEventProcessingSteps> processingSteps;
@@ -91,7 +91,7 @@
 
     ASSERT(isMainThread());
     
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
     if (m_receivedBeganEventFromMainThread || !wheelEvent.isGestureStart())
         return;
 
@@ -105,7 +105,7 @@
 {
     ASSERT(ScrollingThread::isCurrentThread());
 
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
     m_receivedBeganEventFromMainThread = false;
 }
 
@@ -116,13 +116,14 @@
     if (!wheelEvent.isGestureStart())
         return;
 
-    LockHolder locker(m_treeMutex);
+    Locker locker { m_treeLock };
 
     static constexpr auto maxAllowableMainThreadDelay = 50_ms;
     auto startTime = MonotonicTime::now();
     auto timeoutTime = startTime + maxAllowableMainThreadDelay;
 
-    bool receivedEvent = m_waitingForBeganEventCondition.waitUntil(m_treeMutex, timeoutTime, [&] {
+    bool receivedEvent = m_waitingForBeganEventCondition.waitUntil(m_treeLock, timeoutTime, [&] {
+        assertIsHeld(m_treeLock);
         return m_receivedBeganEventFromMainThread;
     });
 
@@ -141,7 +142,7 @@
     // ScrollingCoordinator's page is destroyed.
     ASSERT(ScrollingThread::isCurrentThread());
 
-    LockHolder treeLocker(m_treeMutex);
+    Locker locker { m_treeLock };
     
     removeAllNodes();
     m_delayedRenderingUpdateDetectionTimer = nullptr;
@@ -279,14 +280,16 @@
 
     tracePoint(ScrollingThreadRenderUpdateSyncStart);
 
-    // Wait for the scrolling thread to acquire m_treeMutex. This ensures that any pending wheel events are processed.
+    // Wait for the scrolling thread to acquire m_treeLock. This ensures that any pending wheel events are processed.
     BinarySemaphore semaphore;
     ScrollingThread::dispatch([protectedThis = makeRef(*this), &semaphore]() {
-        LockHolder treeLocker(protectedThis->m_treeMutex);
+        Locker treeLocker { protectedThis->m_treeLock };
         semaphore.signal();
         protectedThis->waitForRenderingUpdateCompletionOrTimeout();
     });
     semaphore.wait();
+
+    Locker locker { m_treeLock };
     m_state = SynchronizationState::InRenderingUpdate;
 }
 
@@ -301,13 +304,13 @@
 // This code allows the main thread about half a frame to complete its rendering udpate. If the main thread
 // is responsive (i.e. managing to render every frame), then we expect to get a didCompleteRenderingUpdate()
 // within 8ms of willStartRenderingUpdate(). We time this via m_stateCondition, which blocks the scrolling
-// thread (with m_treeMutex locked at the start and end) so that we don't handle wheel events while waiting.
+// thread (with m_treeLock locked at the start and end) so that we don't handle wheel events while waiting.
 // If the condition times out, we know the main thread is being slow, and allow the scrolling thread to
 // commit layer positions.
 void ThreadedScrollingTree::waitForRenderingUpdateCompletionOrTimeout()
 {
     ASSERT(ScrollingThread::isCurrentThread());
-    ASSERT(m_treeMutex.isLocked());
+    ASSERT(m_treeLock.isLocked());
 
     if (m_delayedRenderingUpdateDetectionTimer)
         m_delayedRenderingUpdateDetectionTimer->stop();
@@ -315,11 +318,12 @@
     auto startTime = MonotonicTime::now();
     auto timeoutTime = startTime + maxAllowableRenderingUpdateDurationForSynchronization();
 
-    bool becameIdle = m_stateCondition.waitUntil(m_treeMutex, timeoutTime, [&] {
+    bool becameIdle = m_stateCondition.waitUntil(m_treeLock, timeoutTime, [&] {
+        assertIsHeld(m_treeLock);
         return m_state == SynchronizationState::Idle;
     });
 
-    ASSERT(m_treeMutex.isLocked());
+    ASSERT(m_treeLock.isLocked());
 
     if (!becameIdle) {
         m_state = SynchronizationState::Desynchronized;
@@ -335,7 +339,7 @@
 void ThreadedScrollingTree::didCompleteRenderingUpdate()
 {
     ASSERT(isMainThread());
-    LockHolder treeLocker(m_treeMutex);
+    Locker locker { m_treeLock };
 
     if (m_state == SynchronizationState::InRenderingUpdate)
         m_stateCondition.notifyOne();
@@ -346,7 +350,7 @@
 void ThreadedScrollingTree::scheduleDelayedRenderingUpdateDetectionTimer(Seconds delay)
 {
     ASSERT(ScrollingThread::isCurrentThread());
-    ASSERT(m_treeMutex.isLocked());
+    ASSERT(m_treeLock.isLocked());
 
     if (!m_delayedRenderingUpdateDetectionTimer)
         m_delayedRenderingUpdateDetectionTimer = makeUnique<RunLoop::Timer<ThreadedScrollingTree>>(RunLoop::current(), this, &ThreadedScrollingTree::delayedRenderingUpdateDetectionTimerFired);
@@ -358,7 +362,7 @@
 {
     ASSERT(ScrollingThread::isCurrentThread());
 
-    LockHolder treeLocker(m_treeMutex);
+    Locker locker { m_treeLock };
     if (canUpdateLayersOnScrollingThread())
         applyLayerPositionsInternal();
     m_state = SynchronizationState::Desynchronized;
@@ -369,7 +373,7 @@
     TraceScope tracingScope(ScrollingThreadDisplayDidRefreshStart, ScrollingThreadDisplayDidRefreshEnd, displayID());
     ASSERT(ScrollingThread::isCurrentThread());
 
-    LockHolder treeLocker(m_treeMutex);
+    Locker locker { m_treeLock };
 
     if (m_state != SynchronizationState::Idle && canUpdateLayersOnScrollingThread())
         applyLayerPositionsInternal();

Modified: trunk/Source/WebCore/page/scrolling/ThreadedScrollingTree.h (277892 => 277893)


--- trunk/Source/WebCore/page/scrolling/ThreadedScrollingTree.h	2021-05-21 22:14:58 UTC (rev 277892)
+++ trunk/Source/WebCore/page/scrolling/ThreadedScrollingTree.h	2021-05-21 22:17:33 UTC (rev 277893)
@@ -29,7 +29,7 @@
 
 #include "ScrollingStateTree.h"
 #include "ScrollingTree.h"
-#include <wtf/Condition.h>
+#include <wtf/CheckedCondition.h>
 #include <wtf/RefPtr.h>
 #include <wtf/RunLoop.h>
 
@@ -60,7 +60,7 @@
     void willStartRenderingUpdate();
     void didCompleteRenderingUpdate();
 
-    Lock& treeMutex() { return m_treeMutex; }
+    CheckedLock& treeLock() WTF_RETURNS_LOCK(m_treeLock) { return m_treeLock; }
 
     bool scrollAnimatorEnabled() const { return m_scrollAnimatorEnabled; }
 
@@ -84,14 +84,14 @@
 
 private:
     bool isThreadedScrollingTree() const override { return true; }
-    void propagateSynchronousScrollingReasons(const HashSet<ScrollingNodeID>&) override;
+    void propagateSynchronousScrollingReasons(const HashSet<ScrollingNodeID>&) WTF_REQUIRES_LOCK(m_treeLock) override;
 
     void displayDidRefreshOnScrollingThread();
-    void waitForRenderingUpdateCompletionOrTimeout();
+    void waitForRenderingUpdateCompletionOrTimeout() WTF_REQUIRES_LOCK(m_treeLock);
 
-    bool canUpdateLayersOnScrollingThread() const;
+    bool canUpdateLayersOnScrollingThread() const WTF_REQUIRES_LOCK(m_treeLock);
 
-    void scheduleDelayedRenderingUpdateDetectionTimer(Seconds);
+    void scheduleDelayedRenderingUpdateDetectionTimer(Seconds) WTF_REQUIRES_LOCK(m_treeLock);
     void delayedRenderingUpdateDetectionTimerFired();
 
     Seconds maxAllowableRenderingUpdateDurationForSynchronization();
@@ -103,17 +103,17 @@
         Desynchronized,
     };
 
-    SynchronizationState m_state { SynchronizationState::Idle };
-    Condition m_stateCondition;
+    SynchronizationState m_state WTF_GUARDED_BY_LOCK(m_treeLock) { SynchronizationState::Idle };
+    CheckedCondition m_stateCondition;
 
-    bool m_receivedBeganEventFromMainThread { false };
-    Condition m_waitingForBeganEventCondition;
+    bool m_receivedBeganEventFromMainThread WTF_GUARDED_BY_LOCK(m_treeLock) { false };
+    CheckedCondition m_waitingForBeganEventCondition;
 
     // Dynamically allocated because it has to use the ScrollingThread's runloop.
-    std::unique_ptr<RunLoop::Timer<ThreadedScrollingTree>> m_delayedRenderingUpdateDetectionTimer;
+    std::unique_ptr<RunLoop::Timer<ThreadedScrollingTree>> m_delayedRenderingUpdateDetectionTimer WTF_GUARDED_BY_LOCK(m_treeLock);
 
-    bool m_scrollAnimatorEnabled { false };
-    bool m_hasNodesWithSynchronousScrollingReasons { false };
+    const bool m_scrollAnimatorEnabled { false };
+    bool m_hasNodesWithSynchronousScrollingReasons WTF_GUARDED_BY_LOCK(m_treeLock) { false };
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/page/scrolling/mac/ScrollingTreeScrollingNodeDelegateMac.mm (277892 => 277893)


--- trunk/Source/WebCore/page/scrolling/mac/ScrollingTreeScrollingNodeDelegateMac.mm	2021-05-21 22:14:58 UTC (rev 277892)
+++ trunk/Source/WebCore/page/scrolling/mac/ScrollingTreeScrollingNodeDelegateMac.mm	2021-05-21 22:17:33 UTC (rev 277893)
@@ -220,7 +220,7 @@
 std::unique_ptr<ScrollControllerTimer> ScrollingTreeScrollingNodeDelegateMac::createTimer(Function<void()>&& function)
 {
     return WTF::makeUnique<ScrollControllerTimer>(RunLoop::current(), [function = WTFMove(function), protectedNode = makeRef(scrollingNode())] {
-        LockHolder locker(protectedNode->scrollingTree().treeMutex());
+        Locker locker { protectedNode->scrollingTree().treeLock() };
         function();
     });
 }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to