Title: [254652] trunk
Revision
254652
Author
cdu...@apple.com
Date
2020-01-15 15:55:04 -0800 (Wed, 15 Jan 2020)

Log Message

Regression(r253213) Load hang and high CPU usage when trying to load myuhc.com
https://bugs.webkit.org/show_bug.cgi?id=206315
<rdar://problem/58139842>

Reviewed by Geoffrey Garen.

Source/WebCore:

Starting in r253213, we now throw when trying to do a sync XHR during unload. Unfortunately, this is confusing the script
on myuhc.com and it ends up retrying the sync XHR in a tight loop. To address the issue, I am putting in a safety net which
ignores calls to XMLHttpRequest.send() instead of throwing, once we've reached 5 sync XHR failures during unload.

Throwing is useful because this gives a change for Web authors to fall back to using Beacon API or Fetch KeepAlive if the
sync XHR fails. There is already code out there doing just that. You could imagine content doing more than one sync XHR
during unload, each one with a good beacon API fallback. For this reason, I put in a limit of 5 sync failures before
we stop throwing. Having a limit is important to break bad loops when the content simply retries the same sync XHR load
when the sync XHR send() call throws.

Tests: fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload.html
       fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload.html

* dom/Document.cpp:
(WebCore::Document::didRejectSyncXHRDuringPageDismissal):
(WebCore::Document::shouldIgnoreSyncXHRs const):
* dom/Document.h:
* loader/DocumentThreadableLoader.cpp:
(WebCore::DocumentThreadableLoader::DocumentThreadableLoader):
* xml/XMLHttpRequest.cpp:
(WebCore::XMLHttpRequest::prepareToSend):

LayoutTests:

Add layout test coverage.

* fast/xmlhttprequest/resources/xmlhttprequest-multiple-sync-xhr-during-unload-iframe.html: Added.
* fast/xmlhttprequest/resources/xmlhttprequest-sync-xhr-failure-loop-during-unload-iframe.html: Added.
* fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload-expected.txt: Added.
* fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload.html: Added.
* fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload-expected.txt: Added.
* fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (254651 => 254652)


--- trunk/LayoutTests/ChangeLog	2020-01-15 23:25:16 UTC (rev 254651)
+++ trunk/LayoutTests/ChangeLog	2020-01-15 23:55:04 UTC (rev 254652)
@@ -1,3 +1,20 @@
+2020-01-15  Chris Dumez  <cdu...@apple.com>
+
+        Regression(r253213) Load hang and high CPU usage when trying to load myuhc.com
+        https://bugs.webkit.org/show_bug.cgi?id=206315
+        <rdar://problem/58139842>
+
+        Reviewed by Geoffrey Garen.
+
+        Add layout test coverage.
+
+        * fast/xmlhttprequest/resources/xmlhttprequest-multiple-sync-xhr-during-unload-iframe.html: Added.
+        * fast/xmlhttprequest/resources/xmlhttprequest-sync-xhr-failure-loop-during-unload-iframe.html: Added.
+        * fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload-expected.txt: Added.
+        * fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload.html: Added.
+        * fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload-expected.txt: Added.
+        * fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload.html: Added.
+
 2020-01-15  Commit Queue  <commit-qu...@webkit.org>
 
         Unreviewed, rolling out r254576.

Added: trunk/LayoutTests/fast/xmlhttprequest/resources/xmlhttprequest-multiple-sync-xhr-during-unload-iframe.html (0 => 254652)


--- trunk/LayoutTests/fast/xmlhttprequest/resources/xmlhttprequest-multiple-sync-xhr-during-unload-iframe.html	                        (rev 0)
+++ trunk/LayoutTests/fast/xmlhttprequest/resources/xmlhttprequest-multiple-sync-xhr-during-unload-iframe.html	2020-01-15 23:55:04 UTC (rev 254652)
@@ -0,0 +1,22 @@
+<script>
+let xhrExceptionCount = 0;
+
+function doSyncXHR(i)
+{
+    try {
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", "xmlhttprequest-responsetype-json.json?" + i, false);
+        xhr.send(null);
+    } catch(e) {
+        xhrExceptionCount++;
+        return false;
+    }
+    return true;
+}
+
+_onunload_ = () => {
+    for (let i = 0; i < 5; i++)
+        doSyncXHR(i);
+    top.frameDidUnload(xhrExceptionCount);
+}
+</script>

Added: trunk/LayoutTests/fast/xmlhttprequest/resources/xmlhttprequest-sync-xhr-failure-loop-during-unload-iframe.html (0 => 254652)


--- trunk/LayoutTests/fast/xmlhttprequest/resources/xmlhttprequest-sync-xhr-failure-loop-during-unload-iframe.html	                        (rev 0)
+++ trunk/LayoutTests/fast/xmlhttprequest/resources/xmlhttprequest-sync-xhr-failure-loop-during-unload-iframe.html	2020-01-15 23:55:04 UTC (rev 254652)
@@ -0,0 +1,18 @@
+<script>
+function doSyncXHR()
+{
+    try {
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", "xmlhttprequest-responsetype-json.json", false);
+        xhr.send(null);
+    } catch(e) {
+        return false;
+    }
+    return true;
+}
+
+_onunload_ = () => {
+    while (!doSyncXHR()) { };
+    top.frameDidUnload();
+}
+</script>

Added: trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload-expected.txt (0 => 254652)


--- trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload-expected.txt	2020-01-15 23:55:04 UTC (rev 254652)
@@ -0,0 +1,16 @@
+frame "<!--frame1-->" - has 1 onunload handler(s)
+CONSOLE MESSAGE: line 9: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json?0.
+CONSOLE MESSAGE: line 9: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json?1.
+CONSOLE MESSAGE: line 9: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json?2.
+CONSOLE MESSAGE: line 9: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json?3.
+CONSOLE MESSAGE: line 9: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json?4.
+Makes sure that we throw when doing sync XHRs during unload.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS xhrExceptionCount is 5
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload.html (0 => 254652)


--- trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload.html	                        (rev 0)
+++ trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload.html	2020-01-15 23:55:04 UTC (rev 254652)
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<iframe id="testFrame" src=""
+<script>
+description("Makes sure that we throw when doing sync XHRs during unload.");
+jsTestIsAsync = true;
+
+frameDidUnload = (_xhrExceptionCount) => {
+    xhrExceptionCount = _xhrExceptionCount;
+    shouldBe("xhrExceptionCount", "5");
+    finishJSTest();
+}
+
+_onload_ = () => {
+    testFrame.remove();
+}
+</script>
+</html>

Added: trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload-expected.txt (0 => 254652)


--- trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload-expected.txt	2020-01-15 23:55:04 UTC (rev 254652)
@@ -0,0 +1,18 @@
+frame "<!--frame1-->" - has 1 onunload handler(s)
+CONSOLE MESSAGE: line 7: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json.
+CONSOLE MESSAGE: line 7: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json.
+CONSOLE MESSAGE: line 7: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json.
+CONSOLE MESSAGE: line 7: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json.
+CONSOLE MESSAGE: line 7: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json.
+CONSOLE MESSAGE: line 7: XMLHttpRequest cannot load xmlhttprequest-responsetype-json.json.
+CONSOLE MESSAGE: line 7: Ignoring XMLHttpRequest.send() call for 'xmlhttprequest-responsetype-json.json' because the maximum number of synchronous failures was reached.
+Makes sure that retrying failed sync XHRs in a loop during unload does not cause a hang.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS No hang while unloading the iframe
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload.html (0 => 254652)


--- trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload.html	                        (rev 0)
+++ trunk/LayoutTests/fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload.html	2020-01-15 23:55:04 UTC (rev 254652)
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<iframe id="testFrame" src=""
+<script>
+description("Makes sure that retrying failed sync XHRs in a loop during unload does not cause a hang.");
+jsTestIsAsync = true;
+
+frameDidUnload = () => {
+    testPassed("No hang while unloading the iframe");
+    finishJSTest();
+}
+
+_onload_ = () => {
+    testFrame.remove();
+}
+</script>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (254651 => 254652)


--- trunk/Source/WebCore/ChangeLog	2020-01-15 23:25:16 UTC (rev 254651)
+++ trunk/Source/WebCore/ChangeLog	2020-01-15 23:55:04 UTC (rev 254652)
@@ -1,3 +1,33 @@
+2020-01-15  Chris Dumez  <cdu...@apple.com>
+
+        Regression(r253213) Load hang and high CPU usage when trying to load myuhc.com
+        https://bugs.webkit.org/show_bug.cgi?id=206315
+        <rdar://problem/58139842>
+
+        Reviewed by Geoffrey Garen.
+
+        Starting in r253213, we now throw when trying to do a sync XHR during unload. Unfortunately, this is confusing the script
+        on myuhc.com and it ends up retrying the sync XHR in a tight loop. To address the issue, I am putting in a safety net which
+        ignores calls to XMLHttpRequest.send() instead of throwing, once we've reached 5 sync XHR failures during unload.
+
+        Throwing is useful because this gives a change for Web authors to fall back to using Beacon API or Fetch KeepAlive if the
+        sync XHR fails. There is already code out there doing just that. You could imagine content doing more than one sync XHR
+        during unload, each one with a good beacon API fallback. For this reason, I put in a limit of 5 sync failures before
+        we stop throwing. Having a limit is important to break bad loops when the content simply retries the same sync XHR load
+        when the sync XHR send() call throws.
+
+        Tests: fast/xmlhttprequest/xmlhttprequest-multiple-sync-xhr-during-unload.html
+               fast/xmlhttprequest/xmlhttprequest-sync-xhr-failure-loop-during-unload.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::didRejectSyncXHRDuringPageDismissal):
+        (WebCore::Document::shouldIgnoreSyncXHRs const):
+        * dom/Document.h:
+        * loader/DocumentThreadableLoader.cpp:
+        (WebCore::DocumentThreadableLoader::DocumentThreadableLoader):
+        * xml/XMLHttpRequest.cpp:
+        (WebCore::XMLHttpRequest::prepareToSend):
+
 2020-01-15  Ross Kirsling  <ross.kirsl...@sony.com>
 
         Unreviewed build fix for ENABLE_ACCESSIBILITY=OFF following r254566.

Modified: trunk/Source/WebCore/dom/Document.cpp (254651 => 254652)


--- trunk/Source/WebCore/dom/Document.cpp	2020-01-15 23:25:16 UTC (rev 254651)
+++ trunk/Source/WebCore/dom/Document.cpp	2020-01-15 23:55:04 UTC (rev 254652)
@@ -8350,6 +8350,24 @@
         top.setHasEvaluatedUserAgentScripts();
 }
 
+void Document::didRejectSyncXHRDuringPageDismissal()
+{
+    ++m_numberOfRejectedSyncXHRs;
+    if (m_numberOfRejectedSyncXHRs > 1)
+        return;
+
+    postTask([this, weakThis = makeWeakPtr(*this)](auto&) mutable {
+        if (weakThis)
+            m_numberOfRejectedSyncXHRs = 0;
+    });
+}
+
+bool Document::shouldIgnoreSyncXHRs() const
+{
+    const unsigned maxRejectedSyncXHRsPerEventLoopIteration = 5;
+    return m_numberOfRejectedSyncXHRs > maxRejectedSyncXHRsPerEventLoopIteration;
+}
+
 #if ENABLE(APPLE_PAY)
 
 bool Document::isApplePayActive() const

Modified: trunk/Source/WebCore/dom/Document.h (254651 => 254652)


--- trunk/Source/WebCore/dom/Document.h	2020-01-15 23:25:16 UTC (rev 254651)
+++ trunk/Source/WebCore/dom/Document.h	2020-01-15 23:55:04 UTC (rev 254652)
@@ -762,6 +762,9 @@
     void setFocusNavigationStartingNode(Node*);
     Element* focusNavigationStartingNode(FocusDirection) const;
 
+    void didRejectSyncXHRDuringPageDismissal();
+    bool shouldIgnoreSyncXHRs() const;
+
     enum class NodeRemoval { Node, ChildrenOfNode };
     void adjustFocusedNodeOnNodeRemoval(Node&, NodeRemoval = NodeRemoval::Node);
     void adjustFocusNavigationNodeOnNodeRemoval(Node&, NodeRemoval = NodeRemoval::Node);
@@ -2053,7 +2056,7 @@
     RefPtr<Worklet> m_paintWorklet;
     HashMap<String, Ref<PaintWorkletGlobalScope>> m_paintWorkletGlobalScopes;
 #endif
-
+    unsigned m_numberOfRejectedSyncXHRs { 0 };
     bool m_hasEvaluatedUserAgentScripts { false };
     bool m_isRunningUserScripts { false };
     bool m_mayBeDetachedFromFrame { true };

Modified: trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp (254651 => 254652)


--- trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp	2020-01-15 23:25:16 UTC (rev 254651)
+++ trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp	2020-01-15 23:55:04 UTC (rev 254652)
@@ -133,6 +133,7 @@
     ASSERT(m_async || m_referrer.isEmpty());
 
     if (document.settings().disallowSyncXHRDuringPageDismissalEnabled() && !m_async && (!document.page() || !document.page()->areSynchronousLoadsAllowed())) {
+        document.didRejectSyncXHRDuringPageDismissal();
         logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, request.url(), "Synchronous loads are not allowed at this time"));
         return;
     }

Modified: trunk/Source/WebCore/xml/XMLHttpRequest.cpp (254651 => 254652)


--- trunk/Source/WebCore/xml/XMLHttpRequest.cpp	2020-01-15 23:25:16 UTC (rev 254651)
+++ trunk/Source/WebCore/xml/XMLHttpRequest.cpp	2020-01-15 23:55:04 UTC (rev 254652)
@@ -413,6 +413,11 @@
 
     auto& context = *scriptExecutionContext();
 
+    if (is<Document>(context) && downcast<Document>(context).shouldIgnoreSyncXHRs()) {
+        logConsoleError(scriptExecutionContext(), makeString("Ignoring XMLHttpRequest.send() call for '", m_url.string(), "' because the maximum number of synchronous failures was reached."));
+        return ExceptionOr<void> { };
+    }
+
     if (readyState() != OPENED || m_sendFlag)
         return ExceptionOr<void> { Exception { InvalidStateError } };
     ASSERT(!m_loader);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to