Save a crash log when the web process crashes
authoraroben@apple.com <aroben@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 1 Mar 2011 18:13:54 +0000 (18:13 +0000)
committeraroben@apple.com <aroben@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 1 Mar 2011 18:13:54 +0000 (18:13 +0000)
On Windows, WebKitTestRunner now detects when the web process is crashing and waits to exit
until it has finished crashing, which guarantees that the crash log will have had time to be
saved, too. On Mac, we always wait until ReportCrash has exited before capturing the crash
log, so all we have to do is choose the right crash log out of the CrashReporter directory.

Fixes <http://webkit.org/b/44121> <rdar://problem/8320759> When the web process crashes and
a crash log is being saved, WebKitTestRunner thinks the web process has become unresponsive

Reviewed by Sam Weinig.

* Scripts/old-run-webkit-tests:
(testCrashedOrTimedOut): Don't kill WebKitTestRunner when the web process crashes. It will
kill itself. On Windows, this will cause us to wait until the crash log has been saved. On
Mac, it should have no effect. Capture saved crash logs for web process crashes, too.
(captureSavedCrashLog): Added $webProcessCrashed argument. On Mac, look for
WebProces_*.crash files when the web process crashes.

* WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:
(WTR::InjectedBundle::initialize): Added an initializationUserData argument. Updated for
initializePlatformDefaults -> platformInitialize rename. Pass the initializationUserData
along to platformInitialize.

* WebKitTestRunner/InjectedBundle/InjectedBundle.h: See above.

* WebKitTestRunner/InjectedBundle/InjectedBundleMain.cpp:
(WKBundleInitialize): Pass along the initializationUserData to the InjectedBundle.

* WebKitTestRunner/InjectedBundle/mac/InjectedBundleMac.mm:
(WTR::InjectedBundle::platformInitialize):
* WebKitTestRunner/InjectedBundle/qt/InjectedBundleQt.cpp:
(WTR::InjectedBundle::platformInitialize):
Updated function signature.

* WebKitTestRunner/InjectedBundle/win/InjectedBundleWin.cpp:
(WTR::exceptionFilter): Added. Tells the UI process we're crashing by signaling the
webProcessCrashingEvent, then lets the crash continue as normal.

(WTR::InjectedBundle::platformInitialize): Hook up exceptionFilter. Retrieve the name of the
event we should use to tell the UI process we're crashing from the initializationUserData,
and get a handle to that event.

* WebKitTestRunner/TestController.cpp:
(WTR::TestController::TestController): Initialize new members.
(WTR::TestController::processDidCrash): Removed unnecessary WKPageRef argument. Changed to
only print the "#CRASHED - WebProcess" message once, since this can be called more than once
when a crash log is being saved on Windows. Exit right away if specified. (This is the
default.)

* WebKitTestRunner/TestController.h: Added new members.

* WebKitTestRunner/win/TestControllerWin.cpp:
(WTR::TestController::platformInitialize): Set up the event the web process will use to tell
us it's crashing.
(WTR::TestController::platformRunUntil): Pass MWMO_INPUTAVAILABLE to
::MsgWaitForMultipleObjectsEx so we'll process messages that have already been seen by
::PeekMessage. (This is unrelated to the bug fix.) Notice when the webProcessCrashingEvent
has been signaled. When this happens, print the "#CRASHED - WebProcess" message right away
so the test harness will know the web process has crashed and not try to kill us, then wait
for the web process to finish crashing so a crash log will have time to be saved.
(WTR::toWK): Simple hepler function.
(WTR::TestController::platformInitializeContext): Pass along the name of the event the web
process should use to tell us it is crashing in the context's initialization user data.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@80009 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/old-run-webkit-tests
Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp
Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h
Tools/WebKitTestRunner/InjectedBundle/InjectedBundleMain.cpp
Tools/WebKitTestRunner/InjectedBundle/mac/InjectedBundleMac.mm
Tools/WebKitTestRunner/InjectedBundle/qt/InjectedBundleQt.cpp
Tools/WebKitTestRunner/InjectedBundle/win/InjectedBundleWin.cpp
Tools/WebKitTestRunner/TestController.cpp
Tools/WebKitTestRunner/TestController.h
Tools/WebKitTestRunner/win/TestControllerWin.cpp

index ce9c4b2229cb6bc15d474798232a5a3a6ecbb2d8..df8a646706c4ccddedca0d02856cc75128c42472 100644 (file)
@@ -1,3 +1,70 @@
+2011-03-01  Adam Roben  <aroben@apple.com>
+
+        Save a crash log when the web process crashes
+
+        On Windows, WebKitTestRunner now detects when the web process is crashing and waits to exit
+        until it has finished crashing, which guarantees that the crash log will have had time to be
+        saved, too. On Mac, we always wait until ReportCrash has exited before capturing the crash
+        log, so all we have to do is choose the right crash log out of the CrashReporter directory.
+
+        Fixes <http://webkit.org/b/44121> <rdar://problem/8320759> When the web process crashes and
+        a crash log is being saved, WebKitTestRunner thinks the web process has become unresponsive
+
+        Reviewed by Sam Weinig.
+
+        * Scripts/old-run-webkit-tests:
+        (testCrashedOrTimedOut): Don't kill WebKitTestRunner when the web process crashes. It will
+        kill itself. On Windows, this will cause us to wait until the crash log has been saved. On
+        Mac, it should have no effect. Capture saved crash logs for web process crashes, too.
+        (captureSavedCrashLog): Added $webProcessCrashed argument. On Mac, look for
+        WebProces_*.crash files when the web process crashes.
+
+        * WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:
+        (WTR::InjectedBundle::initialize): Added an initializationUserData argument. Updated for
+        initializePlatformDefaults -> platformInitialize rename. Pass the initializationUserData
+        along to platformInitialize.
+
+        * WebKitTestRunner/InjectedBundle/InjectedBundle.h: See above.
+
+        * WebKitTestRunner/InjectedBundle/InjectedBundleMain.cpp:
+        (WKBundleInitialize): Pass along the initializationUserData to the InjectedBundle.
+
+        * WebKitTestRunner/InjectedBundle/mac/InjectedBundleMac.mm:
+        (WTR::InjectedBundle::platformInitialize):
+        * WebKitTestRunner/InjectedBundle/qt/InjectedBundleQt.cpp:
+        (WTR::InjectedBundle::platformInitialize):
+        Updated function signature.
+
+        * WebKitTestRunner/InjectedBundle/win/InjectedBundleWin.cpp:
+        (WTR::exceptionFilter): Added. Tells the UI process we're crashing by signaling the
+        webProcessCrashingEvent, then lets the crash continue as normal.
+
+        (WTR::InjectedBundle::platformInitialize): Hook up exceptionFilter. Retrieve the name of the
+        event we should use to tell the UI process we're crashing from the initializationUserData,
+        and get a handle to that event.
+
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::TestController::TestController): Initialize new members.
+        (WTR::TestController::processDidCrash): Removed unnecessary WKPageRef argument. Changed to
+        only print the "#CRASHED - WebProcess" message once, since this can be called more than once
+        when a crash log is being saved on Windows. Exit right away if specified. (This is the
+        default.)
+
+        * WebKitTestRunner/TestController.h: Added new members.
+
+        * WebKitTestRunner/win/TestControllerWin.cpp:
+        (WTR::TestController::platformInitialize): Set up the event the web process will use to tell
+        us it's crashing.
+        (WTR::TestController::platformRunUntil): Pass MWMO_INPUTAVAILABLE to
+        ::MsgWaitForMultipleObjectsEx so we'll process messages that have already been seen by
+        ::PeekMessage. (This is unrelated to the bug fix.) Notice when the webProcessCrashingEvent
+        has been signaled. When this happens, print the "#CRASHED - WebProcess" message right away
+        so the test harness will know the web process has crashed and not try to kill us, then wait
+        for the web process to finish crashing so a crash log will have time to be saved.
+        (WTR::toWK): Simple hepler function.
+        (WTR::TestController::platformInitializeContext): Pass along the name of the event the web
+        process should use to tell us it is crashing in the context's initialization user data.
+
 2011-03-01  Dimitri Glazkov  <dglazkov@chromium.org>
 
         Reviewed by Tony Gentilcore.
index d5b3289550cc7f68bc6cc55c85772c6b90f44933..6fb9ce54bcb5e59a28864b86fe4bb3296f509329 100755 (executable)
@@ -77,7 +77,7 @@ use POSIX;
 
 sub buildPlatformResultHierarchy();
 sub buildPlatformTestHierarchy(@);
-sub captureSavedCrashLog($);
+sub captureSavedCrashLog($$);
 sub checkPythonVersion();
 sub closeCygpaths();
 sub closeDumpTool();
@@ -1735,11 +1735,13 @@ sub testCrashedOrTimedOut($$$$$$)
 
     recordActualResultsAndDiff($base, $actual);
 
-    kill 9, $dumpToolPID unless $didCrash;
+    # There's no point in killing the dump tool when it's crashed. And it will kill itself when the
+    # web process crashes.
+    kill 9, $dumpToolPID unless $didCrash || $webProcessCrashed;
 
     closeDumpTool();
 
-    captureSavedCrashLog($base) if $didCrash;
+    captureSavedCrashLog($base, $webProcessCrashed) if $didCrash || $webProcessCrashed;
 
     return unless isCygwin() && !$didCrash && $base =~ /^http/;
     # On Cygwin, http tests timing out can be a symptom of a non-responsive httpd.
@@ -1748,9 +1750,9 @@ sub testCrashedOrTimedOut($$$$$$)
     configureAndOpenHTTPDIfNeeded();
 }
 
-sub captureSavedCrashLog($)
+sub captureSavedCrashLog($$)
 {
-    my ($base) = @_;
+    my ($base, $webProcessCrashed) = @_;
 
     my $crashLog;
 
@@ -1758,7 +1760,7 @@ sub captureSavedCrashLog($)
     if (isCygwin()) {
         $glob = File::Spec->catfile($testResultsDirectory, $windowsCrashLogFilePrefix . "*.txt");
     } elsif (isAppleMacWebKit()) {
-        $glob = File::Spec->catfile("~", "Library", "Logs", "CrashReporter", $dumpToolName . "_*.crash");
+        $glob = File::Spec->catfile("~", "Library", "Logs", "CrashReporter", ($webProcessCrashed ? "WebProcess" : $dumpToolName) . "_*.crash");
 
         # Even though the dump tool has exited, CrashReporter might still be running. We need to
         # wait for it to exit to ensure it has saved its crash log to disk. For simplicitly, we'll
index b7bebd47f4b8ff8b34501209b1493b8e9c557b99..00933bd0c444b97a3e2b8a2603a8c57daa89b70a 100644 (file)
@@ -72,7 +72,7 @@ void InjectedBundle::didReceiveMessage(WKBundleRef bundle, WKStringRef messageNa
     static_cast<InjectedBundle*>(const_cast<void*>(clientInfo))->didReceiveMessage(messageName, messageBody);
 }
 
-void InjectedBundle::initialize(WKBundleRef bundle)
+void InjectedBundle::initialize(WKBundleRef bundle, WKTypeRef initializationUserData)
 {
     m_bundle = bundle;
 
@@ -86,7 +86,7 @@ void InjectedBundle::initialize(WKBundleRef bundle)
     };
     WKBundleSetClient(m_bundle, &client);
 
-    initializePlatformDefaults();
+    platformInitialize(initializationUserData);
 
     activateFonts();
     WKBundleActivateMacFontAscentHack(m_bundle);
index 9cfe60cf35686a3bfaecc9fa52d1cd961e1114e4..41b1b31da4d3ac429cf23e3fbdb804e19d01530a 100644 (file)
@@ -45,7 +45,7 @@ public:
     static InjectedBundle& shared();
 
     // Initialize the InjectedBundle.
-    void initialize(WKBundleRef);
+    void initialize(WKBundleRef, WKTypeRef initializationUserData);
 
     WKBundleRef bundle() const { return m_bundle; }
     WKBundlePageGroupRef pageGroup() const { return m_pageGroup; }
@@ -82,7 +82,7 @@ private:
     void didInitializePageGroup(WKBundlePageGroupRef);
     void didReceiveMessage(WKStringRef messageName, WKTypeRef messageBody);
 
-    void initializePlatformDefaults();
+    void platformInitialize(WKTypeRef initializationUserData);
     void resetLocalSettings();
 
     void beginTesting();
index c4cf8927a13a4914270a2d7bbae0d2e9ac46287e..4e7f58b569b7f17f8eabea710a7e2f97b92bcfec 100644 (file)
@@ -33,5 +33,5 @@ extern "C"
 #endif
 void WKBundleInitialize(WKBundleRef bundle, WKTypeRef initializationUserData)
 {
-    WTR::InjectedBundle::shared().initialize(bundle);
+    WTR::InjectedBundle::shared().initialize(bundle, initializationUserData);
 }
index e947a80d01d74e5a8425155567622a34f48edf99..7cb8dfac78e5c27bc8b002fd0aef24816111fb6f 100644 (file)
@@ -27,7 +27,7 @@
 
 namespace WTR {
 
-void InjectedBundle::initializePlatformDefaults()
+void InjectedBundle::platformInitialize(WKTypeRef)
 {
     NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
         [NSNumber numberWithInteger:4],   @"AppleAntiAliasingThreshold",
index aa0b8ef4701a5d5b876f2fea67eabf6f0d635150..52502edffc7a345136a6bd572ee1ed7c53aee215 100644 (file)
@@ -27,7 +27,7 @@
 
 namespace WTR {
 
-void InjectedBundle::initializePlatformDefaults()
+void InjectedBundle::platformInitialize(WKTypeRef)
 {
 }
 
index aa0b8ef4701a5d5b876f2fea67eabf6f0d635150..af8eaedce779d435ec779d6b76f133451a9ce191 100644 (file)
 
 namespace WTR {
 
-void InjectedBundle::initializePlatformDefaults()
+static HANDLE webProcessCrashingEvent;
+
+static LONG WINAPI exceptionFilter(EXCEPTION_POINTERS*)
+{
+    // Let the UI process know right away that we crashed. It might take a long time for us to
+    // finish crashing if a crash log is being saved.
+    ::SetEvent(webProcessCrashingEvent);
+
+    return EXCEPTION_CONTINUE_SEARCH;
+}
+
+void InjectedBundle::platformInitialize(WKTypeRef initializationUserData)
 {
+    ::SetUnhandledExceptionFilter(exceptionFilter);
+
+    ASSERT_ARG(initializationUserData, initializationUserData);
+    ASSERT_ARG(initializationUserData, WKGetTypeID(initializationUserData) == WKStringGetTypeID());
+
+    WKStringRef string = static_cast<WKStringRef>(initializationUserData);
+    Vector<char> buffer(WKStringGetMaximumUTF8CStringSize(string));
+    WKStringGetUTF8CString(string, buffer.data(), buffer.size());
+
+    // The UI process should already have created this event. We're just getting another HANDLE to it.
+    webProcessCrashingEvent = ::CreateEventA(0, FALSE, FALSE, buffer.data());
+    ASSERT(webProcessCrashingEvent);
 }
 
 } // namespace WTR
index 194214b15f6248db80df0f2d2c8d8037c58aae24..d12fff66d845e7d9aacd932d0d3667f35cf2562f 100644 (file)
@@ -63,6 +63,8 @@ TestController::TestController(int argc, const char* argv[])
     , m_doneResetting(false)
     , m_longTimeout(defaultLongTimeout)
     , m_shortTimeout(defaultShortTimeout)
+    , m_didPrintWebProcessCrashedMessage(false)
+    , m_shouldExitWhenWebProcessCrashes(true)
 {
     initialize(argc, argv);
     controller = this;
@@ -470,7 +472,7 @@ void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame, WKT
 
 void TestController::processDidCrash(WKPageRef page, const void* clientInfo)
 {
-    static_cast<TestController*>(const_cast<void*>(clientInfo))->processDidCrash(page);
+    static_cast<TestController*>(const_cast<void*>(clientInfo))->processDidCrash();
 }
 
 void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame)
@@ -489,10 +491,18 @@ void TestController::didFinishLoadForFrame(WKPageRef page, WKFrameRef frame)
     shared().notifyDone();
 }
 
-void TestController::processDidCrash(WKPageRef page)
+void TestController::processDidCrash()
 {
-    fputs("#CRASHED - WebProcess\n", stderr);
-    fflush(stderr);
+    // This function can be called multiple times when crash logs are being saved on Windows, so
+    // ensure we only print the crashed message once.
+    if (!m_didPrintWebProcessCrashedMessage) {
+        fputs("#CRASHED - WebProcess\n", stderr);
+        fflush(stderr);
+        m_didPrintWebProcessCrashedMessage = true;
+    }
+
+    if (m_shouldExitWhenWebProcessCrashes)
+        exit(1);
 }
 
 } // namespace WTR
index fe37952722dc215091c8ffb0921b84aac3dc097f..0630e2b2e859c58e5ae406d64ade4ab36ccb0e14 100644 (file)
@@ -83,7 +83,7 @@ private:
     void didFinishLoadForFrame(WKPageRef page, WKFrameRef frame);
 
     static void processDidCrash(WKPageRef, const void* clientInfo);
-    void processDidCrash(WKPageRef);
+    void processDidCrash();
 
     static WKPageRef createOtherPage(WKPageRef oldPage, WKDictionaryRef, WKEventModifiers, WKEventMouseButton, const void*);
 
@@ -117,6 +117,9 @@ private:
 
     double m_longTimeout;
     double m_shortTimeout;
+
+    bool m_didPrintWebProcessCrashedMessage;
+    bool m_shouldExitWhenWebProcessCrashes;
 };
 
 } // namespace WTR
index f290a1d7770fadcdb750d63e66400c85047e256f..afa5d6e78cbeef3ec44407e9db186c73e17fee6e 100644 (file)
@@ -38,6 +38,9 @@ using namespace std;
 
 namespace WTR {
 
+static HANDLE webProcessCrashingEvent;
+static const char webProcessCrashingEventName[] = "WebKitTestRunner.WebProcessCrashing";
+
 #ifdef DEBUG_ALL
 const LPWSTR testPluginDirectoryName = L"TestNetscapePlugin_Debug";
 const char* injectedBundleDLL = "\\InjectedBundle_debug.dll";
@@ -109,6 +112,8 @@ void TestController::platformInitialize()
     // Add the QuickTime dll directory to PATH or QT 7.6 will fail to initialize on systems
     // linked with older versions of qtmlclientlib.dll.
     addQTDirToPATH();
+
+    webProcessCrashingEvent = ::CreateEventA(0, FALSE, FALSE, webProcessCrashingEventName);
 }
 
 void TestController::initializeInjectedBundlePath()
@@ -137,11 +142,33 @@ void TestController::platformRunUntil(bool& done, double timeout)
         if (now > end)
             return;
 
-        DWORD result = ::MsgWaitForMultipleObjectsEx(0, 0, end - now, QS_ALLINPUT, 0);
+        DWORD result = ::MsgWaitForMultipleObjectsEx(1, &webProcessCrashingEvent, end - now, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
         if (result == WAIT_TIMEOUT)
             return;
 
-        ASSERT(result == WAIT_OBJECT_0);
+        if (result == WAIT_OBJECT_0) {
+            // The web process is crashing. A crash log might be being saved, which can take a long
+            // time, and we don't want to time out while that happens.
+
+            // First, let the test harness know this happened so it won't think we've hung. But
+            // make sure we don't exit just yet!
+            m_shouldExitWhenWebProcessCrashes = false;
+            processDidCrash();
+            m_shouldExitWhenWebProcessCrashes = true;
+
+            // Then spin a run loop until it finishes crashing to give time for a crash log to be saved.
+            MSG msg;
+            while (BOOL bRet = ::GetMessageW(&msg, 0, 0, 0)) {
+                if (bRet == -1)
+                    break;
+                ::TranslateMessage(&msg);
+                ::DispatchMessageW(&msg);
+            }
+
+            exit(1);
+        }
+
+        ASSERT(result == WAIT_OBJECT_0 + 1);
         // There are messages in the queue. Process them.
         MSG msg;
         while (::PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) {
@@ -151,10 +178,17 @@ void TestController::platformRunUntil(bool& done, double timeout)
     }
 }
 
+static WKRetainPtr<WKStringRef> toWK(const char* string)
+{
+    return WKRetainPtr<WKStringRef>(AdoptWK, WKStringCreateWithUTF8CString(string));
+}
+
 void TestController::platformInitializeContext()
 {
     // FIXME: Make DRT pass with Windows native controls. <http://webkit.org/b/25592>
     WKContextSetShouldPaintNativeControls(m_context.get(), false);
+
+    WKContextSetInitializationUserDataForInjectedBundle(m_context.get(), toWK(webProcessCrashingEventName).get());
 }
 
 void TestController::runModal(PlatformWebView*)