Title: [285330] trunk/Source/WebCore
Revision
285330
Author
[email protected]
Date
2021-11-04 20:27:54 -0700 (Thu, 04 Nov 2021)

Log Message

Nested run loops under MediaPlayerPrivateAVFoundationObjC::waitForVideoOutputMediaDataWillChange can cause hang when timeout fires
https://bugs.webkit.org/show_bug.cgi?id=232695
<rdar://problem/85004449>

Reviewed by Jer Noble.

It's possible for MediaPlayerPrivateAVFoundationObjC::waitForVideoOutputMediaDataWillChange
to be called re-entrantly, if the RunLoop::run call ends up processing
an event that also wants to synchronously update the media image. This
can cause a hang:

1. Enter the outer waitForVideoOutputMediaDataWillChange call.
2. Set up the outer timeout timer.
3. Call RunLoop::run.
    3.1. Enter the inner waitForVideoOutputMediaDataWillChange call.
    3.2. Set up the inner timeout timer.
    3.3. Call RunLoop::run.
        3.3.1. Wait for new RunLoop events, and none arrive.
        3.3.2. The outer timeout timer fires, calling RunLoop::stop.
    3.4. Return from waitForVideoOutputMediaDataWillChange, cancelling
         the inner timeout timer.
    3.5. Wait for more events on the run loop, forever.

To avoid this, we can set up a single timeout timer, and track the
nesting level of our RunLoop::run calls. The innermost RunLoop::run call
will finish either by the timer firing (which calls RunLoop::stop) or by
the video data updating (which also calls RunLoop::stop, under
outputMediaDataWillChange). Either way, once the innermost
RunLoop::run call is finished, we know we can stop processing all of
the ancestor RunLoop:run calls.

* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
(WebCore::MediaPlayerPrivateAVFoundationObjC::waitForVideoOutputMediaDataWillChange):
(WebCore::MediaPlayerPrivateAVFoundationObjC::outputMediaDataWillChange):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (285329 => 285330)


--- trunk/Source/WebCore/ChangeLog	2021-11-05 03:25:23 UTC (rev 285329)
+++ trunk/Source/WebCore/ChangeLog	2021-11-05 03:27:54 UTC (rev 285330)
@@ -1,3 +1,41 @@
+2021-11-04  Cameron McCormack  <[email protected]>
+
+        Nested run loops under MediaPlayerPrivateAVFoundationObjC::waitForVideoOutputMediaDataWillChange can cause hang when timeout fires
+        https://bugs.webkit.org/show_bug.cgi?id=232695
+        <rdar://problem/85004449>
+
+        Reviewed by Jer Noble.
+
+        It's possible for MediaPlayerPrivateAVFoundationObjC::waitForVideoOutputMediaDataWillChange
+        to be called re-entrantly, if the RunLoop::run call ends up processing
+        an event that also wants to synchronously update the media image. This
+        can cause a hang:
+
+        1. Enter the outer waitForVideoOutputMediaDataWillChange call.
+        2. Set up the outer timeout timer.
+        3. Call RunLoop::run.
+            3.1. Enter the inner waitForVideoOutputMediaDataWillChange call.
+            3.2. Set up the inner timeout timer.
+            3.3. Call RunLoop::run.
+                3.3.1. Wait for new RunLoop events, and none arrive.
+                3.3.2. The outer timeout timer fires, calling RunLoop::stop.
+            3.4. Return from waitForVideoOutputMediaDataWillChange, cancelling
+                 the inner timeout timer.
+            3.5. Wait for more events on the run loop, forever.
+
+        To avoid this, we can set up a single timeout timer, and track the
+        nesting level of our RunLoop::run calls. The innermost RunLoop::run call
+        will finish either by the timer firing (which calls RunLoop::stop) or by
+        the video data updating (which also calls RunLoop::stop, under
+        outputMediaDataWillChange). Either way, once the innermost
+        RunLoop::run call is finished, we know we can stop processing all of
+        the ancestor RunLoop:run calls.
+
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm:
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::waitForVideoOutputMediaDataWillChange):
+        (WebCore::MediaPlayerPrivateAVFoundationObjC::outputMediaDataWillChange):
+
 2021-11-04  Alan Bujtas  <[email protected]>
 
         [LFC][IFC] Use objectReplacementCharacter to mark atomic inline box position.

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h (285329 => 285330)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h	2021-11-05 03:25:23 UTC (rev 285329)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.h	2021-11-05 03:27:54 UTC (rev 285330)
@@ -428,6 +428,7 @@
     mutable long long m_cachedTotalBytes { 0 };
     unsigned m_pendingStatusChanges { 0 };
     int m_cachedItemStatus;
+    int m_runLoopNestingLevel { 0 };
     MediaPlayer::BufferingPolicy m_bufferingPolicy { MediaPlayer::BufferingPolicy::Default };
     bool m_cachedLikelyToKeepUp { false };
     bool m_cachedBufferEmpty { false };
@@ -447,7 +448,6 @@
     mutable bool m_allowsWirelessVideoPlayback { true };
     bool m_shouldPlayToPlaybackTarget { false };
 #endif
-    bool m_runningModalPaint { false };
     bool m_haveProcessedChapterTracks { false };
     bool m_waitForVideoOutputMediaDataWillChangeTimedOut { false };
     bool m_haveBeenAskedToPaint { false };

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm (285329 => 285330)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm	2021-11-05 03:25:23 UTC (rev 285329)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm	2021-11-05 03:27:54 UTC (rev 285330)
@@ -2609,21 +2609,31 @@
 {
     if (m_waitForVideoOutputMediaDataWillChangeTimedOut)
         return;
-    [m_videoOutput requestNotificationOfMediaDataChangeWithAdvanceInterval:0];
 
     // Wait for 1 second.
     MonotonicTime start = MonotonicTime::now();
 
-    RunLoop::Timer<MediaPlayerPrivateAVFoundationObjC> timeoutTimer { RunLoop::main(), [] {
-        RunLoop::main().stop();
-    } };
-    timeoutTimer.startOneShot(1_s);
+    std::optional<RunLoop::Timer<MediaPlayerPrivateAVFoundationObjC>> timeoutTimer;
 
-    m_runningModalPaint = true;
+    if (!m_runLoopNestingLevel) {
+        [m_videoOutput requestNotificationOfMediaDataChangeWithAdvanceInterval:0];
+
+        timeoutTimer.emplace(RunLoop::main(), [&] {
+            RunLoop::main().stop();
+        });
+        timeoutTimer->startOneShot(1_s);
+    }
+
+    ++m_runLoopNestingLevel;
     RunLoop::run();
-    m_runningModalPaint = false;
+    --m_runLoopNestingLevel;
 
-    bool satisfied = timeoutTimer.isActive();
+    if (m_runLoopNestingLevel) {
+        RunLoop::main().stop();
+        return;
+    }
+
+    bool satisfied = timeoutTimer->isActive();
     if (!satisfied) {
         ERROR_LOG(LOGIDENTIFIER, "timed out");
         m_waitForVideoOutputMediaDataWillChangeTimedOut = true;
@@ -2633,7 +2643,7 @@
 
 void MediaPlayerPrivateAVFoundationObjC::outputMediaDataWillChange()
 {
-    if (m_runningModalPaint) {
+    if (m_runLoopNestingLevel) {
         if (RunLoop::isMain())
             RunLoop::main().stop();
         else {
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to