|
40 | 40 | import org.labkey.api.resource.Resource; |
41 | 41 | import org.labkey.api.script.ScriptReference; |
42 | 42 | import org.labkey.api.script.ScriptService; |
| 43 | +import org.labkey.api.security.User; |
| 44 | +import org.labkey.api.settings.AppProps; |
| 45 | +import org.labkey.api.settings.WriteableAppProps; |
43 | 46 | import org.labkey.api.test.TestWhen; |
44 | 47 | import org.labkey.api.util.HeartBeat; |
45 | 48 | import org.labkey.api.util.JunitUtil; |
46 | 49 | import org.labkey.api.util.MemTracker; |
47 | 50 | import org.labkey.api.util.Path; |
| 51 | +import org.labkey.api.util.TestContext; |
48 | 52 | import org.labkey.api.util.UnexpectedException; |
49 | 53 | import org.labkey.api.view.HttpView; |
50 | 54 | import org.mozilla.javascript.ClassShutter; |
@@ -199,6 +203,44 @@ public void reportTest() throws Exception |
199 | 203 | test("reportTest"); |
200 | 204 | } |
201 | 205 |
|
| 206 | + @Test |
| 207 | + public void timeoutTest() throws Exception |
| 208 | + { |
| 209 | + int original = AppProps.getInstance().getScriptExecutionTimeout(); |
| 210 | + User user = TestContext.get().getUser(); |
| 211 | + |
| 212 | + try |
| 213 | + { |
| 214 | + // Busy-loop bounded at 15 seconds of wall-clock time; the 1-second watchdog should abort it long before that. HeartBeat ticks once per second, so the abort lands after 2-3 real seconds. |
| 215 | + setScriptExecutionTimeout(1, user); |
| 216 | + try |
| 217 | + { |
| 218 | + RhinoService.RHINO_FACTORY.getScriptEngine().eval("var start = new Date().getTime(); while (new Date().getTime() - start < 15000) {}"); |
| 219 | + fail("Expected script to be terminated by the execution timeout"); |
| 220 | + } |
| 221 | + catch (Exception e) |
| 222 | + { |
| 223 | + assertTrue("Unexpected script error: " + e.getMessage(), e.getMessage().contains("Script execution exceeded 1 seconds")); |
| 224 | + } |
| 225 | + |
| 226 | + // 0 disables the watchdog: a loop that runs well past the 1-second timeout above should complete normally |
| 227 | + setScriptExecutionTimeout(0, user); |
| 228 | + Object result = RhinoService.RHINO_FACTORY.getScriptEngine().eval("var start = new Date().getTime(); while (new Date().getTime() - start < 2500) {} 'completed';"); |
| 229 | + assertEquals("completed", result); |
| 230 | + } |
| 231 | + finally |
| 232 | + { |
| 233 | + setScriptExecutionTimeout(original, user); |
| 234 | + } |
| 235 | + } |
| 236 | + |
| 237 | + private void setScriptExecutionTimeout(int seconds, User user) |
| 238 | + { |
| 239 | + WriteableAppProps props = AppProps.getWriteableInstance(); |
| 240 | + props.setScriptExecutionTimeout(seconds); |
| 241 | + props.save(user); |
| 242 | + } |
| 243 | + |
202 | 244 | private void test(String scriptName) throws ScriptException, NoSuchMethodException |
203 | 245 | { |
204 | 246 | Path js = Path.parse(ScriptService.SCRIPTS_DIR + "/validationTest/" + scriptName + ".js"); |
@@ -1006,9 +1048,8 @@ protected void observeInstructionCount(Context cx, int instructionCount) |
1006 | 1048 | { |
1007 | 1049 | SandboxContext ctx = (SandboxContext)cx; |
1008 | 1050 | long currentTime = HeartBeat.currentTimeMillis(); |
1009 | | - final int timeout = 60; |
1010 | | - if (currentTime - ctx.startTime > timeout*1000) |
1011 | | - Context.reportError("Script execution exceeded " + timeout + " seconds."); |
| 1051 | + if (ctx.timeoutSeconds > 0 && currentTime - ctx.startTime > ctx.timeoutSeconds * 1000L) |
| 1052 | + Context.reportError("Script execution exceeded " + ctx.timeoutSeconds + " seconds."); |
1012 | 1053 | } |
1013 | 1054 |
|
1014 | 1055 | @Override |
@@ -1044,12 +1085,15 @@ public boolean visibleToScripts(String fullClassName) |
1044 | 1085 | private static class SandboxContext extends Context |
1045 | 1086 | { |
1046 | 1087 | private final long startTime; |
| 1088 | + // resolved once per context; observeInstructionCount runs far too often for a property lookup |
| 1089 | + private final int timeoutSeconds; |
1047 | 1090 |
|
1048 | 1091 | private SandboxContext(SandboxContextFactory factory) |
1049 | 1092 | { |
1050 | 1093 | super(factory); |
1051 | 1094 | setLanguageVersion(Context.VERSION_1_8); |
1052 | 1095 | startTime = HeartBeat.currentTimeMillis(); |
| 1096 | + timeoutSeconds = AppProps.getInstance().getScriptExecutionTimeout(); |
1053 | 1097 | } |
1054 | 1098 | } |
1055 | 1099 |
|
|
0 commit comments