From bdef5e6d96641f66e194cf4fc35046f22c4e2c05 Mon Sep 17 00:00:00 2001
From: Mick Jordan <mick.jordan@oracle.com>
Date: Wed, 7 Dec 2016 15:49:25 -0800
Subject: [PATCH] Use Truffle TimeBoxing to timeout unit tests

---
 .../truffle/r/test/generate/FastRSession.java | 199 ++++++------------
 1 file changed, 68 insertions(+), 131 deletions(-)

diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/FastRSession.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/FastRSession.java
index 76f72535f0..8ec3a05b2b 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/FastRSession.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/generate/FastRSession.java
@@ -26,11 +26,12 @@ import java.util.ArrayDeque;
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.TimeZone;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
+import java.util.Timer;
+import java.util.TimerTask;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.debug.Debugger;
+import com.oracle.truffle.api.debug.SuspendedCallback;
+import com.oracle.truffle.api.debug.SuspendedEvent;
 import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.vm.PolyglotEngine;
 import com.oracle.truffle.r.runtime.RCmdOptions;
@@ -133,8 +134,6 @@ public final class FastRSession implements RSession {
     private final PolyglotEngine main;
     private final RContext mainContext;
 
-    private EvalThread evalThread;
-
     public static FastRSession create() {
         if (singleton == null) {
             singleton = new FastRSession();
@@ -191,143 +190,81 @@ public final class FastRSession implements RSession {
      * {@link #eval} but also used for package installation via the {@code system2} command, where
      * the result is used to check whether the installation succeeded.
      */
-    @SuppressWarnings("deprecation")
     public Object evalAsObject(TestBase testClass, String expression, ContextInfo contextInfo, boolean longTimeout) throws Throwable {
+        Object result = null;
+        Timer timer = null;
         consoleHandler.reset();
-        EvalThread thread = evalThread;
-        if (thread == null || !thread.isAlive() || contextInfo != thread.contextInfo) {
-            thread = new EvalThread(contextInfo);
-            thread.setName("FastR evaluation");
-            thread.start();
-            evalThread = thread;
-        }
-
-        thread.push(testClass, expression);
-
         try {
-            if (!thread.await(longTimeout ? longTimeoutValue : timeoutValue)) {
-                consoleHandler.println("<timeout>");
-                System.out.println("timeout in " + testClass.getClass() + ": " + expression);
-                for (StackTraceElement element : thread.getStackTrace()) {
-                    System.out.println(element);
-                }
-                thread.stop();
-                evalThread.ensureContextDestroyed();
-                evalThread = null;
-                throw new TimeoutException();
+            ContextInfo actualContextInfo = checkContext(contextInfo);
+            // set up some interop objects used by fastr-specific tests:
+            PolyglotEngine.Builder builder = PolyglotEngine.newBuilder();
+            if (testClass != null) {
+                testClass.addPolyglotSymbols(builder);
             }
-        } catch (InterruptedException e1) {
-            e1.printStackTrace();
-        }
-        if (thread.killedByException != null) {
-            evalThread = null;
-            throw thread.killedByException;
-        }
-        return evalThread.result;
-    }
-
-    private final class EvalThread extends RContext.ContextThread {
-
-        private volatile String expression;
-        private volatile Throwable killedByException;
-        private final Semaphore entry = new Semaphore(0);
-        private final Semaphore exit = new Semaphore(0);
-        private final ContextInfo contextInfo;
-        private TestBase testClass;
-        private Object result;
-
-        /**
-         * Create an evaluation thread (to handle timeouts).
-         *
-         * @param contextInfo {@code null} for a lightweight test context, else an existing one to
-         *            use.
-         */
-        EvalThread(ContextInfo contextInfo) {
-            super(null);
-            this.contextInfo = contextInfo;
-            setDaemon(true);
-        }
-
-        public void push(TestBase testClassArg, String exp) {
-            this.expression = exp;
-            this.testClass = testClassArg;
-            this.entry.release();
-        }
-
-        public boolean await(int millisTimeout) throws InterruptedException {
-            return exit.tryAcquire(millisTimeout, TimeUnit.MILLISECONDS);
-        }
-
-        /**
-         * In case the vm is not disposed by the {@code finally} clause in run after a timeout,
-         * (which should not happen), we explicitly destroy the context, to avoid subsequent errors
-         * relating to multiple children of a single SHARED_RW context.
-         */
-        public void ensureContextDestroyed() {
-            context.destroy();
-        }
-
-        @Override
-        public void run() {
-            while (killedByException == null) {
-                try {
-                    entry.acquire();
-                } catch (InterruptedException e) {
-                    break;
-                }
-                try {
-                    ContextInfo actualContextInfo = checkContext(contextInfo);
-                    // set up some interop objects used by fastr-specific tests:
-                    PolyglotEngine.Builder builder = PolyglotEngine.newBuilder();
-                    if (testClass != null) {
-                        testClass.addPolyglotSymbols(builder);
-                    }
-                    PolyglotEngine vm = actualContextInfo.createVM(builder);
-                    consoleHandler.setInput(expression.split("\n"));
+            PolyglotEngine vm = actualContextInfo.createVM(builder);
+            timer = scheduleTimeBoxing(vm, longTimeout ? longTimeoutValue : timeoutValue);
+            consoleHandler.setInput(expression.split("\n"));
+            try {
+                String input = consoleHandler.readLine();
+                while (input != null) {
+                    Source source = RSource.fromTextInternal(input, RSource.Internal.UNIT_TEST);
                     try {
-                        String input = consoleHandler.readLine();
-                        while (input != null) {
-                            Source source = RSource.fromTextInternal(input, RSource.Internal.UNIT_TEST);
-                            try {
-                                try {
-                                    result = vm.eval(source).get();
-                                    // checked exceptions are wrapped in RuntimeExceptions
-                                } catch (RuntimeException e) {
-                                    if (e.getCause() instanceof com.oracle.truffle.api.vm.IncompleteSourceException) {
-                                        throw e.getCause().getCause();
-                                    } else {
-                                        throw e;
-                                    }
-                                }
-                                input = consoleHandler.readLine();
-                            } catch (IncompleteSourceException e) {
-                                String additionalInput = consoleHandler.readLine();
-                                if (additionalInput == null) {
-                                    throw e;
-                                }
-                                input += "\n" + additionalInput;
+                        try {
+                            result = vm.eval(source).get();
+                            // checked exceptions are wrapped in RuntimeExceptions
+                        } catch (RuntimeException e) {
+                            if (e.getCause() instanceof com.oracle.truffle.api.vm.IncompleteSourceException) {
+                                throw e.getCause().getCause();
+                            } else {
+                                throw e;
                             }
                         }
-                    } finally {
-                        vm.dispose();
-                    }
-                } catch (ParseException e) {
-                    e.report(consoleHandler);
-                } catch (RError e) {
-                    // nothing to do
-                } catch (Throwable t) {
-                    if (!TestBase.ProcessFailedTests) {
-                        if (t instanceof RInternalError) {
-                            RInternalError.reportError(t);
+                        input = consoleHandler.readLine();
+                    } catch (IncompleteSourceException e) {
+                        String additionalInput = consoleHandler.readLine();
+                        if (additionalInput == null) {
+                            throw e;
                         }
-                        t.printStackTrace();
+                        input += "\n" + additionalInput;
                     }
-                    killedByException = t;
-                } finally {
-                    exit.release();
                 }
+            } finally {
+                vm.dispose();
+            }
+        } catch (ParseException e) {
+            e.report(consoleHandler);
+        } catch (RError e) {
+            // nothing to do
+        } catch (Throwable t) {
+            if (!TestBase.ProcessFailedTests) {
+                if (t instanceof RInternalError) {
+                    RInternalError.reportError(t);
+                }
+                t.printStackTrace();
+            }
+            throw t;
+        } finally {
+            if (timer != null) {
+                timer.cancel();
             }
         }
+        return result;
+    }
+
+    private static Timer scheduleTimeBoxing(PolyglotEngine engine, long timeout) {
+        Timer timer = new Timer();
+        timer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                Debugger.find(engine).startSession(new SuspendedCallback() {
+                    @Override
+                    public void onSuspend(SuspendedEvent event) {
+                        event.prepareKill();
+                    }
+                }).suspendNextExecution();
+            }
+        }, timeout);
+        return timer;
     }
 
     @Override
-- 
GitLab