You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2016/04/28 15:59:39 UTC

svn commit: r1741426 - in /commons/proper/jexl/trunk: ./ src/main/java/org/apache/commons/jexl3/internal/ src/site/xdoc/ src/test/java/org/apache/commons/jexl3/

Author: henrib
Date: Thu Apr 28 13:59:38 2016
New Revision: 1741426

URL: http://svn.apache.org/viewvc?rev=1741426&view=rev
Log:
JEXL:
Fixing JEXL-193, refined handling of interruption (InterruptedException handling & interrupted()), more checks around cancellation (blocks,set/map/array literals), refactored internal Callable, more tests

Modified:
    commons/proper/jexl/trunk/RELEASE-NOTES.txt
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Script.java
    commons/proper/jexl/trunk/src/site/xdoc/changes.xml
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java

Modified: commons/proper/jexl/trunk/RELEASE-NOTES.txt
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/RELEASE-NOTES.txt?rev=1741426&r1=1741425&r2=1741426&view=diff
==============================================================================
--- commons/proper/jexl/trunk/RELEASE-NOTES.txt (original)
+++ commons/proper/jexl/trunk/RELEASE-NOTES.txt Thu Apr 28 13:59:38 2016
@@ -28,6 +28,7 @@ Version 3.0.1 is a micro release to fix
 Bugs Fixed in 3.0.1:
 ====================
 
+* JEXL-193:     InterruptedException is swallowed in function call in silent and non-strict mode
 * JEXL-192:     Invalid return type when expected result is null
 * JEXL-191:     Jexl3 unsolvable property exception when using enum
 * JEXL-190:     local function within context is not resolved if function resolver class without namespace is specified

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java?rev=1741426&r1=1741425&r2=1741426&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java Thu Apr 28 13:59:38 2016
@@ -20,7 +20,6 @@ import org.apache.commons.jexl3.JexlCont
 import org.apache.commons.jexl3.parser.ASTJexlLambda;
 import org.apache.commons.jexl3.parser.JexlNode;
 
-import java.util.concurrent.Callable;
 
 /**
  * A Script closure.
@@ -125,23 +124,16 @@ public class Closure extends Script {
     }
 
     @Override
-    public Callable<Object> callable(JexlContext context, Object... args) {
+    public Callable callable(JexlContext context, Object... args) {
         Scope.Frame local = null;
         if (frame != null) {
             local = frame.assign(args);
         }
-        final Interpreter interpreter = jexl.createInterpreter(context, local);
-        return new Callable<Object>() {
-            /** Use interpreter as marker for not having run. */
-            private Object result = interpreter;
-
+        return new Callable(jexl.createInterpreter(context, local)) {
             @Override
-            public Object call() throws Exception {
-                if (result == interpreter) {
-                    JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
-                    result = interpreter.interpret(block);
-                }
-                return result;
+            public Object interpret() {
+                JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
+                return interpreter.interpret(block);
             }
         };
     }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java?rev=1741426&r1=1741425&r2=1741426&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java Thu Apr 28 13:59:38 2016
@@ -200,14 +200,17 @@ public class Interpreter extends ParserV
             }
             return node.jjtAccept(this, null);
         } catch (JexlException.Return xreturn) {
-            Object value = xreturn.getValue();
-            return value;
+            return xreturn.getValue();
+        } catch (JexlException.Cancel xcancel) {
+            cancelled |= Thread.interrupted();
+            if (!silent && strictEngine) {
+                throw xcancel.clean();
+            }
         } catch (JexlException xjexl) {
-            if (silent) {
-                logger.warn(xjexl.getMessage(), xjexl.getCause());
-                return null;
+            if (!silent) {
+                throw xjexl.clean();
             }
-            throw xjexl.clean();
+            logger.warn(xjexl.getMessage(), xjexl.getCause());
         } finally {
             if (functors != null && AUTOCLOSEABLE != null) {
                 for (Object functor : functors.values()) {
@@ -225,6 +228,7 @@ public class Interpreter extends ParserV
                 jexl.putThreadLocal(local);
             }
         }
+        return null;
     }
 
     /** Java7 AutoCloseable interface defined?. */
@@ -336,21 +340,51 @@ public class Interpreter extends ParserV
         if (!silent) {
             logger.warn(xjexl.getMessage(), xjexl.getCause());
         }
-        if (strictEngine || xjexl instanceof JexlException.Return) {
+        if (strictEngine
+            || xjexl instanceof JexlException.Return
+            || xjexl instanceof JexlException.Cancel) {
             throw xjexl;
         }
         return null;
     }
 
     /**
-     * Checks whether this interpreter execution was cancelled due to thread interruption.
-     * @return true if cancelled, false otherwise
+     * Wraps an exception thrown by an invocation.
+     * @param node the node triggering the exception
+     * @param methodName the method/function name
+     * @param xany the cause
+     * @return a JexlException
      */
-    protected boolean isCancelled() {
-        if (cancelled || Thread.currentThread().isInterrupted()) {
+    protected JexlException invocationException(JexlNode node, String methodName, Exception xany) {
+        Throwable cause = xany.getCause();
+        if (cause instanceof JexlException) {
+            throw (JexlException) cause;
+        }
+        if (cause instanceof InterruptedException) {
             cancelled = true;
+            return new JexlException.Cancel(node);
         }
-        return cancelled;
+        return new JexlException(node, methodName, xany);
+    }
+
+    /**
+     * Checks whether this interpreter execution was canceled due to thread interruption.
+     * @return true if canceled, false otherwise
+     */
+    protected boolean isCancelled() {
+         if (!cancelled) {
+             cancelled = Thread.currentThread().isInterrupted();
+         }
+         return cancelled;
+    }
+
+    /**
+     * Cancels this evaluation, setting the cancel flag that will result in a JexlException.Cancel to be thrown.
+     * @return true in all cases
+     */
+    protected boolean cancel() {
+         cancelled = true;
+         return cancelled;
     }
 
     /**
@@ -722,6 +756,9 @@ public class Interpreter extends ParserV
         int numChildren = node.jjtGetNumChildren();
         Object result = null;
         for (int i = 0; i < numChildren; i++) {
+            if (isCancelled()) {
+                throw new JexlException.Cancel(node);
+            }
             result = node.jjtGetChild(i).jjtAccept(this, data);
         }
         return result;
@@ -897,6 +934,9 @@ public class Interpreter extends ParserV
         if (ab != null) {
             boolean extended = false;
             for (int i = 0; i < childCount; i++) {
+                if (isCancelled()) {
+                    throw new JexlException.Cancel(node);
+                }
                 JexlNode child = node.jjtGetChild(i);
                 if (child instanceof ASTExtendedLiteral) {
                     extended = true;
@@ -922,6 +962,9 @@ public class Interpreter extends ParserV
         JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount);
         if (mb != null) {
             for (int i = 0; i < childCount; i++) {
+                if (isCancelled()) {
+                    throw new JexlException.Cancel(node);
+                }
                 Object entry = node.jjtGetChild(i).jjtAccept(this, data);
                 mb.add(entry);
             }
@@ -937,6 +980,9 @@ public class Interpreter extends ParserV
         JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount);
         if (mb != null) {
             for (int i = 0; i < childCount; i++) {
+                if (isCancelled()) {
+                    throw new JexlException.Cancel(node);
+                }
                 Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
                 mb.put(entry[0], entry[1]);
             }
@@ -1016,6 +1062,9 @@ public class Interpreter extends ParserV
             for (int i = 0; i < numChildren; i++) {
                 JexlNode child = node.jjtGetChild(i);
                 result = child.jjtAccept(this, data);
+                if (isCancelled()) {
+                    throw new JexlException.Cancel(child);
+                }
             }
             return result;
         }
@@ -1067,6 +1116,9 @@ public class Interpreter extends ParserV
                 return null;
             }
             Object index = nindex.jjtAccept(this, null);
+            if (isCancelled()) {
+                throw new JexlException.Cancel(node);
+            }
             object = getAttribute(object, index, nindex);
         }
         return object;
@@ -1135,6 +1187,9 @@ public class Interpreter extends ParserV
             }
             // attempt to evaluate the property within the object (visit(ASTIdentifierAccess node))
             object = objectNode.jjtAccept(this, object);
+            if (isCancelled()) {
+                throw new JexlException.Cancel(node);
+            }
             if (object != null) {
                 // disallow mixing antish variable & bean with same root; avoid ambiguity
                 antish = false;
@@ -1710,7 +1765,7 @@ public class Interpreter extends ParserV
         } catch (JexlException.Method xmethod) {
             throw xmethod;
         } catch (Exception xany) {
-            xjexl = new JexlException(node, methodName, xany);
+            xjexl = invocationException(node, methodName, xany);
         }
         return invocationFailed(xjexl);
     }
@@ -1763,7 +1818,7 @@ public class Interpreter extends ParserV
             throw xmethod;
         } catch (Exception xany) {
             String dbgStr = cobject != null ? cobject.toString() : null;
-            xjexl = new JexlException(node, dbgStr, xany);
+            xjexl = invocationException(node, dbgStr, xany);
         }
         return invocationFailed(xjexl);
     }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Script.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Script.java?rev=1741426&r1=1741425&r2=1741426&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Script.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Script.java Thu Apr 28 13:59:38 2016
@@ -26,7 +26,6 @@ import org.apache.commons.jexl3.parser.J
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.Callable;
 
 /**
  * <p>A JexlScript implementation.</p>
@@ -322,7 +321,7 @@ public class Script implements JexlScrip
      * @return the callable
      */
     @Override
-    public Callable<Object> callable(JexlContext context) {
+    public Callable callable(JexlContext context) {
         return callable(context, (Object[]) null);
     }
 
@@ -335,20 +334,45 @@ public class Script implements JexlScrip
      * @return the callable
      */
     @Override
-    public Callable<Object> callable(JexlContext context, Object... args) {
-        final Interpreter interpreter = jexl.createInterpreter(context, script.createFrame(args));
-        return new Callable<Object>() {
-            /** Use interpreter as marker for not having run. */
-            private Object result = interpreter;
+    public Callable callable(JexlContext context, Object... args) {
+        return new Callable(jexl.createInterpreter(context, script.createFrame(args)));
+    }
+
+    /**
+     * Implements the Future and Callable interfaces to help delegation.
+     */
+    public class Callable implements java.util.concurrent.Callable<Object> {
+        /** The actual interpreter. */
+        protected final Interpreter interpreter;
+        /** Use interpreter as marker for not having run. */
+        protected Object result;
+
+        /**
+         * The base constructor.
+         * @param intrprtr the interpreter to use
+         */
+        protected Callable(Interpreter intrprtr) {
+            this.interpreter = intrprtr;
+            this.result = intrprtr;
+        }
 
-            @Override
-            public Object call() throws Exception {
+        /**
+         * Run the interpreter.
+         * @return the evaluation result
+         */
+        protected Object interpret() {
+            return interpreter.interpret(script);
+        }
+
+        @Override
+        public Object call() throws Exception {
+            synchronized(this) {
                 if (result == interpreter) {
                     checkCacheVersion();
-                    result = interpreter.interpret(script);
+                    result = interpret();
                 }
                 return result;
             }
-        };
+        }
     }
 }
\ No newline at end of file

Modified: commons/proper/jexl/trunk/src/site/xdoc/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/site/xdoc/changes.xml?rev=1741426&r1=1741425&r2=1741426&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/changes.xml Thu Apr 28 13:59:38 2016
@@ -26,6 +26,9 @@
     </properties>
     <body>
         <release version="3.0.1" date="unreleased">
+            <action dev="henrib" type="fix" issue="JEXL-193" due-to="Dmitri Blinov">
+                InterruptedException is swallowed in function call in silent and non-strict mode
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-192" due-to="Constantin Mitocaru">
                 Invalid return type when expected result is null
             </action>

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java?rev=1741426&r1=1741425&r2=1741426&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java Thu Apr 28 13:59:38 2016
@@ -1123,25 +1123,26 @@ public class IssuesTest extends JexlTest
     }
 
     public static class C192 {
-        public C192() {}
+        public C192() {
+        }
 
-       public static Integer callme(Integer n) {
-           if (n == null) {
-               return null;
-           } else {
-               return n >= 0? 42 : -42;
-           }
-       }
+        public static Integer callme(Integer n) {
+            if (n == null) {
+                return null;
+            } else {
+                return n >= 0 ? 42 : -42;
+            }
+        }
 
-       public static Object kickme() {
-           return C192.class;
-       }
+        public static Object kickme() {
+            return C192.class;
+        }
     }
 
     @Test
     public void test192() throws Exception {
         JexlContext jc = new MapContext();
-        jc.set("x.y.z",  C192.class);
+        jc.set("x.y.z", C192.class);
         JexlEngine jexl = new JexlBuilder().create();
         JexlExpression js0 = jexl.createExpression("x.y.z.callme(t)");
         jc.set("t", null);
@@ -1162,58 +1163,4 @@ public class IssuesTest extends JexlTest
         jc.set("t", null);
         Assert.assertNull(js0.evaluate(jc));
     }
-//
-//
-//	@Test
-//	public void testUnderscoreInName() {
-//        JexlEngine jexl = new Engine();
-//        String jexlExp = "(x.length_mm * x.width)";
-//        JexlExpression e = jexl.createExpression( jexlExp );
-//        JexlContext jc = new MapContext();
-//
-//        LazyDynaMap object = new LazyDynaMap();
-//        object.set("length_mm", "10.0");
-//        object.set("width", "5.0");
-//
-//        jc.set("x", object );
-//
-//	    Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
-//   }
-//
-//	@Test
-//	public void testFullStopInName() {
-//        JexlEngine jexl = new Engine();
-//        String jexlExp = "(x.length.mm * x.width)";
-//        JexlExpression e = jexl.createExpression( jexlExp );
-//        JexlContext jc = new MapContext();
-//
-//        LazyDynaMap object = new LazyDynaMap();
-//        object.set("length.mm", "10.0");
-//        object.set("width", "5.0");
-//
-//        Assert.assertEquals(null, object.get("length.mm"), "10.0");
-//
-//        jc.set("x", object );
-//
-//	    Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
-//	}
-//
-//	@Test
-//	public void testFullStopInNameMakingSubObject() {
-//        JexlEngine jexl = new Engine();
-//        String jexlExp = "(x.length.mm * x.width)";
-//        JexlExpression e = jexl.createExpression( jexlExp );
-//        JexlContext jc = new MapContext();
-//
-//        LazyDynaMap object = new LazyDynaMap();
-//        LazyDynaMap subObject = new LazyDynaMap();
-//        object.set("length", subObject);
-//        subObject.set("mm", "10.0");
-//        object.set("width", "5.0");
-//
-//        jc.set("x", object );
-//
-//	    Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
-//	}
-
 }

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java?rev=1741426&r1=1741425&r2=1741426&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ScriptCallableTest.java Thu Apr 28 13:59:38 2016
@@ -17,6 +17,8 @@
 package org.apache.commons.jexl3;
 
 import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -29,6 +31,7 @@ import org.junit.Test;
 /**
  * Tests around asynchronous script execution and interrupts.
  */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
 public class ScriptCallableTest extends JexlTestCase {
     //Logger LOGGER = Logger.getLogger(VarTest.class.getName());
     public ScriptCallableTest() {
@@ -42,50 +45,61 @@ public class ScriptCallableTest extends
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
         executor.submit(future);
+        Object t = 42;
         try {
-            future.get(100, TimeUnit.MILLISECONDS);
+            t = future.get(100, TimeUnit.MILLISECONDS);
             Assert.fail("should have timed out");
         } catch (TimeoutException xtimeout) {
             // ok, ignore
+            future.cancel(true);
+        } finally {
+            executor.shutdown();
         }
-        Thread.sleep(100);
-        future.cancel(true);
 
         Assert.assertTrue(future.isCancelled());
+        Assert.assertEquals(42, t);
     }
 
     @Test
     public void testCallable() throws Exception {
         JexlScript e = JEXL.createScript("while(true);");
         Callable<Object> c = e.callable(null);
+        Object t = 42;
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
         Future<?> future = executor.submit(c);
         try {
-            future.get(100, TimeUnit.MILLISECONDS);
+            t = future.get(100, TimeUnit.MILLISECONDS);
             Assert.fail("should have timed out");
         } catch (TimeoutException xtimeout) {
             // ok, ignore
+            future.cancel(true);
+        } finally {
+            executor.shutdown();
         }
-        future.cancel(true);
         Assert.assertTrue(future.isCancelled());
+        Assert.assertEquals(42, t);
     }
 
     @Test
     public void testCallableClosure() throws Exception {
         JexlScript e = JEXL.createScript("function(t) {while(t);}");
         Callable<Object> c = e.callable(null, Boolean.TRUE);
+        Object t = 42;
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
         Future<?> future = executor.submit(c);
         try {
-            future.get(100, TimeUnit.MILLISECONDS);
+            t = future.get(100, TimeUnit.MILLISECONDS);
             Assert.fail("should have timed out");
         } catch (TimeoutException xtimeout) {
             // ok, ignore
+            future.cancel(true);
+        } finally {
+            executor.shutdown();
         }
-        future.cancel(true);
         Assert.assertTrue(future.isCancelled());
+        Assert.assertEquals(42, t);
     }
 
     public static class TestContext extends MapContext implements JexlContext.NamespaceResolver {
@@ -118,6 +132,19 @@ public class ScriptCallableTest extends
             }
             return 1;
         }
+
+        public int interrupt() throws InterruptedException {
+            Thread.currentThread().interrupt();
+            return 42;
+        }
+
+        public void sleep(long millis) throws InterruptedException {
+            try {
+                Thread.sleep(millis);
+            } catch (InterruptedException xint) {
+                throw xint;
+            }
+        }
     }
 
     @Test
@@ -126,10 +153,14 @@ public class ScriptCallableTest extends
         Callable<Object> c = e.callable(new TestContext());
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
-        Future<?> future = executor.submit(c);
-        Object t = future.get(2, TimeUnit.SECONDS);
-        Assert.assertTrue(future.isDone());
-        Assert.assertEquals(0, t);
+        try {
+            Future<?> future = executor.submit(c);
+            Object t = future.get(2, TimeUnit.SECONDS);
+            Assert.assertTrue(future.isDone());
+            Assert.assertEquals(0, t);
+        } finally {
+            executor.shutdown();
+        }
     }
 
     @Test
@@ -138,9 +169,13 @@ public class ScriptCallableTest extends
         Callable<Object> c = e.callable(new TestContext());
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
-        Future<?> future = executor.submit(c);
-        Object t = future.get(2, TimeUnit.SECONDS);
-        Assert.assertEquals(1, t);
+        try {
+            Future<?> future = executor.submit(c);
+            Object t = future.get(2, TimeUnit.SECONDS);
+            Assert.assertEquals(1, t);
+        } finally {
+            executor.shutdown();
+        }
     }
 
     @Test
@@ -149,15 +184,21 @@ public class ScriptCallableTest extends
         Callable<Object> c = e.callable(new TestContext());
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
-        Future<?> future = executor.submit(c);
         try {
-            future.get(100, TimeUnit.MILLISECONDS);
-            Assert.fail("should have timed out");
-        } catch (TimeoutException xtimeout) {
-            // ok, ignore
+            Future<?> future = executor.submit(c);
+            Object t = 42;
+            try {
+                t = future.get(100, TimeUnit.MILLISECONDS);
+                Assert.fail("should have timed out");
+            } catch (TimeoutException xtimeout) {
+                // ok, ignore
+                future.cancel(true);
+            }
+            Assert.assertTrue(future.isCancelled());
+            Assert.assertEquals(42, t);
+        } finally {
+            executor.shutdown();
         }
-        future.cancel(true);
-        Assert.assertTrue(future.isCancelled());
     }
 
     @Test
@@ -167,14 +208,19 @@ public class ScriptCallableTest extends
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
         Future<?> future = executor.submit(c);
+        Object t = 42;
+
         try {
-            future.get(100, TimeUnit.MILLISECONDS);
+            t = future.get(100, TimeUnit.MILLISECONDS);
             Assert.fail("should have timed out");
         } catch (TimeoutException xtimeout) {
             // ok, ignore
+            future.cancel(true);
+        } finally {
+            executor.shutdown();
         }
-        future.cancel(true);
         Assert.assertTrue(future.isCancelled());
+        Assert.assertEquals(42, t);
     }
 
     @Test
@@ -184,14 +230,19 @@ public class ScriptCallableTest extends
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
         Future<?> future = executor.submit(c);
+        Object t = 42;
+
         try {
-            future.get(100, TimeUnit.MILLISECONDS);
+            t = future.get(100, TimeUnit.MILLISECONDS);
             Assert.fail("should have timed out");
         } catch (TimeoutException xtimeout) {
             // ok, ignore
+            future.cancel(true);
+        } finally {
+            executor.shutdown();
         }
-        future.cancel(true);
         Assert.assertTrue(future.isCancelled());
+        Assert.assertEquals(42, t);
     }
 
     @Test
@@ -201,13 +252,147 @@ public class ScriptCallableTest extends
 
         ExecutorService executor = Executors.newFixedThreadPool(1);
         Future<?> future = executor.submit(c);
+        Object t = 42;
+
         try {
-            future.get(100, TimeUnit.MILLISECONDS);
+            t = future.get(100, TimeUnit.MILLISECONDS);
             Assert.fail("should have timed out");
         } catch (TimeoutException xtimeout) {
-            // ok, ignore
+            future.cancel(true);
+        } finally {
+            executor.shutdown();
         }
-        future.cancel(true);
         Assert.assertTrue(future.isCancelled());
+        Assert.assertEquals(42, t);
+    }
+
+    @Test
+    public void testInterruptVerboseStrict() throws Exception {
+        runInterrupt(false, true);
+    }
+
+    @Test
+    public void testInterruptVerboseLenient() throws Exception {
+        runInterrupt(false, false);
+    }
+
+    @Test
+    public void testInterruptSilentStrict() throws Exception {
+        runInterrupt(true, true);
+    }
+
+    @Test
+    public void testInterruptSilentLenient() throws Exception {
+        runInterrupt(true, true);
+    }
+
+    /**
+     * Redundant test with previous ones but impervious to JEXL engine configuation.
+     * @param silent silent engine flag
+     * @param strict strict (aka not lenient) engine flag
+     * @throws Exception if there is a regression
+     */
+    private void runInterrupt(boolean silent, boolean strict) throws Exception {
+        ExecutorService exec = Executors.newFixedThreadPool(2);
+        try {
+            JexlContext ctxt = new TestContext();
+            JexlEngine jexl = new JexlBuilder().silent(silent).strict(strict).create();
+
+            // run an interrupt
+            JexlScript sint = jexl.createScript("interrupt(); return 42");
+            Object t = null;
+            try {
+                Callable<Object> c = sint.callable(ctxt);
+                t = c.call();
+            } catch (JexlException.Cancel xjexl) {
+                if (silent || !strict) {
+                    Assert.fail("should not have thrown " + xjexl);
+                }
+            }
+            Assert.assertNotEquals(42, t);
+
+            // self interrupt
+            Future<Object> c = null;
+            try {
+                c = exec.submit(sint.callable(ctxt));
+                t = c.get();
+            } catch (ExecutionException xexec) {
+                if (silent || !strict) {
+                    Assert.fail("should not have thrown " + xexec);
+                }
+            }
+            Assert.assertNotEquals(42, t);
+
+            // timeout a sleep
+            JexlScript ssleep = jexl.createScript("sleep(30000); return 42");
+            try {
+                c = exec.submit(ssleep.callable(ctxt));
+                t = c.get(100L, TimeUnit.MILLISECONDS);
+                Assert.fail("should timeout");
+            } catch (TimeoutException xtimeout) {
+                if (c != null) {
+                    c.cancel(true);
+                }
+            }
+            Assert.assertNotEquals(42, t);
+
+            // cancel a sleep
+            try {
+                final Future<Object> fc = exec.submit(ssleep.callable(ctxt));
+                Runnable cancels = new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            Thread.sleep(200L);
+                        } catch (Exception xignore) {
+
+                        }
+                        fc.cancel(true);
+                    }
+                };
+                exec.submit(cancels);
+                t = c.get();
+                Assert.fail("should be cancelled");
+            } catch (CancellationException xexec) {
+                // this is the expected result
+            }
+
+            // timeout a while(true)
+            JexlScript swhile = jexl.createScript("while(true); return 42");
+            try {
+                c = exec.submit(swhile.callable(ctxt));
+                t = c.get(100L, TimeUnit.MILLISECONDS);
+                Assert.fail("should timeout");
+            } catch (TimeoutException xtimeout) {
+                if (c != null) {
+                    c.cancel(true);
+                }
+            }
+            Assert.assertNotEquals(42, t);
+
+            // cancel a while(true)
+            try {
+                final Future<Object> fc = exec.submit(swhile.callable(ctxt));
+                Runnable cancels = new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            Thread.sleep(200L);
+                        } catch (Exception xignore) {
+
+                        }
+                        fc.cancel(true);
+                    }
+                };
+                exec.submit(cancels);
+                t = c.get();
+                Assert.fail("should be cancelled");
+            } catch (CancellationException xexec) {
+                // this is the expected result
+            }
+            Assert.assertNotEquals(42, t);
+        } finally {
+            exec.shutdown();
+        }
     }
 }