changeset 4877:fbab6330a073

RT-21460: Unit test javafx.scene.web.IrresponsiveScriptTest hangs
author Vasiliy Baranov <vasiliy.baranov@oracle.com>
date Wed, 28 Aug 2013 12:27:23 +0400
parents 1e133a8c7a60
children fa2f68455df7
files build.gradle modules/web/src/main/java/com/sun/webkit/WatchdogTimer.java modules/web/src/main/native/Source/JavaScriptCore/TargetJava.pri modules/web/src/main/native/Source/JavaScriptCore/runtime/Watchdog.cpp modules/web/src/main/native/Source/JavaScriptCore/runtime/Watchdog.h modules/web/src/main/native/Source/JavaScriptCore/runtime/WatchdogJava.cpp modules/web/src/main/native/Source/WebCore/mapfile-macosx modules/web/src/main/native/Source/WebCore/mapfile-vers modules/web/src/main/native/Source/WebCore/platform/java/WebPage.cpp modules/web/src/test/java/javafx/scene/web/IrresponsiveScriptTest.java
diffstat 10 files changed, 263 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/build.gradle	Wed Aug 28 11:43:05 2013 +0400
+++ b/build.gradle	Wed Aug 28 12:27:23 2013 +0400
@@ -2069,6 +2069,7 @@
                      "com.sun.webkit.PageCache",
                      "com.sun.webkit.PopupMenu",
                      "com.sun.webkit.SharedBuffer",
+                     "com.sun.webkit.WatchdogTimer",
                      "com.sun.webkit.WebPage",
                      "com.sun.webkit.LoadListenerClient",
                      "com.sun.webkit.event.WCFocusEvent",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/web/src/main/java/com/sun/webkit/WatchdogTimer.java	Wed Aug 28 12:27:23 2013 +0400
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.sun.webkit;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import static java.util.logging.Level.WARNING;
+import java.util.logging.Logger;
+
+final class WatchdogTimer {
+
+    private static final Logger logger =
+            Logger.getLogger(WatchdogTimer.class.getName());
+    private static final ThreadFactory threadFactory =
+            new CustomThreadFactory();
+
+
+    // The executor is intentionally made a non-static/instance
+    // field (as opposed to a static/class field) in order for
+    // fwkDestroy() to be able to orderly shutdown the executor
+    // and wait for the outstanding runnable, if any, to complete.
+    // Currently, there is just a single instance of this class
+    // per process, so having a non-static executor should not
+    // be a problem.
+    private final ScheduledThreadPoolExecutor executor;
+    private final Runnable runnable;
+    private ScheduledFuture<?> future;
+
+
+    private WatchdogTimer(final long nativePointer) {
+        executor = new ScheduledThreadPoolExecutor(1, threadFactory);
+        executor.setRemoveOnCancelPolicy(true);
+
+        runnable = new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    twkFire(nativePointer);
+                } catch (Throwable th) {
+                    logger.log(WARNING, "Error firing watchdog timer", th);
+                }
+            }
+        };
+    }
+
+
+    private static WatchdogTimer fwkCreate(long nativePointer) {
+        return new WatchdogTimer(nativePointer);
+    }
+
+    private void fwkStart(double limit) {
+        if (future != null) {
+            throw new IllegalStateException();
+        }
+        future = executor.schedule(runnable, (long) (limit * 1000) + 50,
+                TimeUnit.MILLISECONDS);
+    }
+
+    private void fwkStop() {
+        if (future == null) {
+            throw new IllegalStateException();
+        }
+        future.cancel(false);
+        future = null;
+    }
+
+    private void fwkDestroy() {
+        executor.shutdownNow();
+        while (true) {
+            try {
+                if (executor.awaitTermination(1, TimeUnit.SECONDS)) {
+                    break;
+                }
+            } catch (InterruptedException ex) {
+                // continue waiting
+            }
+        }
+    }
+
+    private native void twkFire(long nativePointer);
+
+    private static final class CustomThreadFactory implements ThreadFactory {
+        private final ThreadGroup group;
+        private final AtomicInteger index = new AtomicInteger(1);
+
+        private CustomThreadFactory() {
+            SecurityManager s = System.getSecurityManager();
+            group = (s != null) ? s.getThreadGroup()
+                    : Thread.currentThread().getThreadGroup();
+        }
+
+        @Override
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(group, r,
+                    "Watchdog-Timer-" + index.getAndIncrement());
+            t.setDaemon(true);
+            return t;
+        }
+    }
+}
--- a/modules/web/src/main/native/Source/JavaScriptCore/TargetJava.pri	Wed Aug 28 11:43:05 2013 +0400
+++ b/modules/web/src/main/native/Source/JavaScriptCore/TargetJava.pri	Wed Aug 28 12:27:23 2013 +0400
@@ -331,7 +331,7 @@
     runtime/StructureRareData.cpp \
     runtime/SymbolTable.cpp \
     runtime/Watchdog.cpp \
-    runtime/WatchdogNone.cpp \
+    runtime/WatchdogJava.cpp \
     tools/CodeProfile.cpp \
     tools/CodeProfiling.cpp \
     yarr/YarrJIT.cpp \
--- a/modules/web/src/main/native/Source/JavaScriptCore/runtime/Watchdog.cpp	Wed Aug 28 11:43:05 2013 +0400
+++ b/modules/web/src/main/native/Source/JavaScriptCore/runtime/Watchdog.cpp	Wed Aug 28 12:27:23 2013 +0400
@@ -95,6 +95,15 @@
     if (!m_timerDidFire)
         return false;
     m_timerDidFire = false;
+
+#if PLATFORM(JAVA)
+    if (m_isStopped) {
+        // If the timer is stopped, it is not counting and m_startTime
+        // is junk so it does not make any sense to proceed
+        return false;
+    }
+#endif
+
     stopCountdown();
 
     double currentTime = currentCPUTime();
@@ -150,8 +159,18 @@
 void Watchdog::disarm()
 {
     ASSERT(m_reentryCount > 0);
+#if PLATFORM(JAVA)
+    if (m_reentryCount == 1) {
+        stopCountdown();
+        // Now that the timer is going out of scope the m_didFire
+        // flag needs to be reset as otherwise the timer would
+        // remain "fired" forever
+        m_didFire = false;
+    }
+#else
     if (m_reentryCount == 1)
         stopCountdown();
+#endif
     m_reentryCount--;
 }
 
--- a/modules/web/src/main/native/Source/JavaScriptCore/runtime/Watchdog.h	Wed Aug 28 11:43:05 2013 +0400
+++ b/modules/web/src/main/native/Source/JavaScriptCore/runtime/Watchdog.h	Wed Aug 28 12:27:23 2013 +0400
@@ -30,6 +30,10 @@
 #include <dispatch/dispatch.h>    
 #endif
 
+#if PLATFORM(JAVA)
+#include <wtf/java/JavaRef.h>
+#endif
+
 namespace JSC {
 
 class ExecState;
@@ -98,6 +102,10 @@
     dispatch_source_t m_timer;
 #endif
 
+#if PLATFORM(JAVA)
+    JGObject m_timer;
+#endif
+
     friend class Watchdog::Scope;
     friend class LLIntOffsetsExtractor;
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/web/src/main/native/Source/JavaScriptCore/runtime/WatchdogJava.cpp	Wed Aug 28 12:27:23 2013 +0400
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include "config.h"
+#include "Watchdog.h"
+
+#include "com_sun_webkit_WatchdogTimer.h"
+
+namespace JSC {
+
+static jclass GetWatchdogTimerClass(JNIEnv* env)
+{
+    static JGClass clazz(env->FindClass(
+            "com/sun/webkit/WatchdogTimer"));
+    ASSERT(clazz);
+    return clazz;
+}
+
+void Watchdog::initTimer()
+{
+    JNIEnv* env = JavaScriptCore_GetJavaEnv();
+
+    static jmethodID mid = env->GetStaticMethodID(
+            GetWatchdogTimerClass(env),
+            "fwkCreate",
+            "(J)Lcom/sun/webkit/WatchdogTimer;");
+    ASSERT(mid);
+
+    m_timer = JLObject(env->CallStaticObjectMethod(
+            GetWatchdogTimerClass(env),
+            mid,
+            ptr_to_jlong(timerDidFireAddress())));
+    CheckAndClearException(env);
+}
+
+void Watchdog::destroyTimer()
+{
+    JNIEnv* env = JavaScriptCore_GetJavaEnv();
+
+    static jmethodID mid = env->GetMethodID(
+            GetWatchdogTimerClass(env),
+            "fwkDestroy",
+            "()V");
+    ASSERT(mid);
+
+    env->CallVoidMethod(m_timer, mid);
+    CheckAndClearException(env);
+
+    m_timer.clear();
+}
+
+void Watchdog::startTimer(double limit)
+{
+    JNIEnv* env = JavaScriptCore_GetJavaEnv();
+
+    static jmethodID mid = env->GetMethodID(
+            GetWatchdogTimerClass(env),
+            "fwkStart",
+            "(D)V");
+    ASSERT(mid);
+
+    env->CallVoidMethod(m_timer, mid, (jdouble) limit);
+    CheckAndClearException(env);
+}
+
+void Watchdog::stopTimer()
+{
+    JNIEnv* env = JavaScriptCore_GetJavaEnv();
+
+    static jmethodID mid = env->GetMethodID(
+            GetWatchdogTimerClass(env),
+            "fwkStop",
+            "()V");
+    ASSERT(mid);
+
+    env->CallVoidMethod(m_timer, mid);
+    CheckAndClearException(env);
+}
+
+} // namespace JSC
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT void JNICALL Java_com_sun_webkit_WatchdogTimer_twkFire
+  (JNIEnv*, jobject, jlong nativePointer)
+{
+    bool* timerDidFireAddress = static_cast<bool*>(jlong_to_ptr(nativePointer));
+    ASSERT(timerDidFireAddress);
+    *timerDidFireAddress = true;
+}
+
+#ifdef __cplusplus
+}
+#endif
--- a/modules/web/src/main/native/Source/WebCore/mapfile-macosx	Wed Aug 28 11:43:05 2013 +0400
+++ b/modules/web/src/main/native/Source/WebCore/mapfile-macosx	Wed Aug 28 12:27:23 2013 +0400
@@ -1836,6 +1836,7 @@
                _Java_com_sun_webkit_WCPluginWidget_twkInvalidateWindowlessPluginRect
                _Java_com_sun_webkit_WCPluginWidget_twkSetPlugunFocused
                _Java_com_sun_webkit_WCWidget_initIDs
+               _Java_com_sun_webkit_WatchdogTimer_twkFire
                _Java_com_sun_webkit_WebPage_twkAddJavaScriptBinding
                _Java_com_sun_webkit_WebPage_twkAdjustFrameHeight
                _Java_com_sun_webkit_WebPage_twkBeginPrinting
--- a/modules/web/src/main/native/Source/WebCore/mapfile-vers	Wed Aug 28 11:43:05 2013 +0400
+++ b/modules/web/src/main/native/Source/WebCore/mapfile-vers	Wed Aug 28 12:27:23 2013 +0400
@@ -1836,6 +1836,7 @@
                Java_com_sun_webkit_WCPluginWidget_twkInvalidateWindowlessPluginRect;
                Java_com_sun_webkit_WCPluginWidget_twkSetPlugunFocused;
                Java_com_sun_webkit_WCWidget_initIDs;
+               Java_com_sun_webkit_WatchdogTimer_twkFire;
                Java_com_sun_webkit_WebPage_twkAddJavaScriptBinding;
                Java_com_sun_webkit_WebPage_twkAdjustFrameHeight;
                Java_com_sun_webkit_WebPage_twkBeginPrinting;
--- a/modules/web/src/main/native/Source/WebCore/platform/java/WebPage.cpp	Wed Aug 28 11:43:05 2013 +0400
+++ b/modules/web/src/main/native/Source/WebCore/platform/java/WebPage.cpp	Wed Aug 28 12:27:23 2013 +0400
@@ -38,6 +38,7 @@
 #include "InspectorClientJava.h"
 #include "InspectorController.h"
 #include "InitializeLogging.h"
+#include "JSContextRefPrivate.h"
 #include "JavaEnv.h"
 #include "JavaRef.h"
 #include "Logging.h"
@@ -816,6 +817,10 @@
     frameLoaderClient->setFrame(frame.get());
 
     frame->init();
+
+    JSGlobalContextRef globalContext = getGlobalContext(frame->script());
+    JSContextGroupRef contextGroup = JSContextGetGroup(globalContext);
+    JSContextGroupSetExecutionTimeLimit(contextGroup, 10, 0, 0);
 }
 
 JNIEXPORT void JNICALL Java_com_sun_webkit_WebPage_twkDestroyPage
--- a/modules/web/src/test/java/javafx/scene/web/IrresponsiveScriptTest.java	Wed Aug 28 11:43:05 2013 +0400
+++ b/modules/web/src/test/java/javafx/scene/web/IrresponsiveScriptTest.java	Wed Aug 28 12:27:23 2013 +0400
@@ -3,16 +3,15 @@
  */
 package javafx.scene.web;
 
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
 import javafx.event.EventHandler;
 import netscape.javascript.JSException;
 import org.junit.Test;
-import static org.junit.Assert.*;
-
 
 public class IrresponsiveScriptTest extends TestBase {
 
-    @org.junit.Ignore @Test public void testInfiniteLoopInScript() {
+    @Test public void testInfiniteLoopInScript() {
         try {
             // This infinite loop should get interrupted by Webkit in about 10s.
             // If it doesn't, test times out.
@@ -26,24 +25,43 @@
             }
         }
     }
-    
-    @Test public void testInfiniteLoopInHandler() {
+
+    @Test public void testLongWaitInHandler() {
         // This test verifies that user code is not subject to Webkit timeout.
         // It installs an alert handler that takes TIMEOUT seconds to run,
         // and checks that it is not interrupted.
         final int TIMEOUT = 24;    // seconds
-        final AtomicBoolean passed = new AtomicBoolean(false);
         getEngine().setOnAlert(new EventHandler<WebEvent<String>>() {
             public void handle(WebEvent<String> ev) {
                 try {
                     synchronized (this) {
                         wait(TIMEOUT * 1000);
                     }
-                    passed.set(true);
                 } catch (InterruptedException e) {
                 }
             }
         });
         executeScript("alert('Jumbo!');");
     }
+
+    @Test public void testLongLoopInHandler() {
+        // This test verifies that user code is not subject to Webkit timeout.
+        // The test installs an alert handler that takes a sufficiently large
+        // amount of CPU time to run, and checks that the handler is not
+        // interrupted.
+        final long CPU_TIME_TO_RUN = 24L * 1000 * 1000 * 1000;
+        getEngine().setOnAlert(new EventHandler<WebEvent<String>>() {
+            public void handle(WebEvent<String> ev) {
+                ThreadMXBean bean = ManagementFactory.getThreadMXBean();
+                long startCpuTime = bean.getCurrentThreadCpuTime();
+                while (bean.getCurrentThreadCpuTime() - startCpuTime
+                        < CPU_TIME_TO_RUN)
+                {
+                    // Do something that consumes CPU time
+                    Math.sqrt(Math.random() * 21082013);
+                }
+            }
+        });
+        executeScript("alert('Jumbo!');");
+    }
 }