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(); + } } }