Skip to content

Instantly share code, notes, and snippets.

@jfrantzius
Last active February 21, 2017 10:01
Show Gist options
  • Save jfrantzius/0a40c963413bdeabb51ecb13a769a436 to your computer and use it in GitHub Desktop.
Save jfrantzius/0a40c963413bdeabb51ecb13a769a436 to your computer and use it in GitHub Desktop.
Failing test that verifies closures aren't thread-safe
package com.aperto.javascript;
import org.junit.Assert;
import org.junit.Test;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
/**
* Some tests on Javascript closure behaviour.
* @author joerg.frantzius
*
*/
public class NashornClosureTest {
/**
* Succeeds because holds function pointer in closure, instead of String or Thread object.
*/
@Test
public void testClosureThreadSafety2() throws ScriptException {
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
String testJsFunction = (
" (function outerFunction(currentThreadFunction) {\n" +
" function innerFunction() {\n" +
" return currentThreadFunction().toString();\n" +
" }\n" +
" return innerFunction;\n" +
" })(java.lang.Thread.currentThread)\n");
ScriptObjectMirror jsFunction = (ScriptObjectMirror) engine.eval(testJsFunction);
IntConsumer invokeAndTest = i-> {
Object received = jsFunction.call(jsFunction);
Assert.assertEquals(Thread.currentThread().toString(), received);
};
IntStream.range(0, 10).parallel().forEach(invokeAndTest);
}
/**
* Fails by design of Javascript closures (using a Thread object instead of String object).
*/
@Test
public void testClosureThreadSafetyWithThreadObject() throws ScriptException {
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
String testJsFunction = (
" (function outerFunction(threadPointer) {\n" +
" function innerFunction() {\n" +
" return threadPointer.toString();\n" +
" }\n" +
" return innerFunction;\n" +
" })(java.lang.Thread.currentThread())\n");
ScriptObjectMirror jsFunction = (ScriptObjectMirror) engine.eval(testJsFunction);
IntConsumer invokeAndTest = i-> {
Object received = jsFunction.call(jsFunction);
Assert.assertEquals(Thread.currentThread().toString(), received);
};
IntStream.range(0, 10).parallel().forEach(invokeAndTest);
}
/**
* Fails by design of Javascript closures.
*/
@Test
public void testClosureThreadSafetyWithString() throws ScriptException {
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
String testJsFunction = (
" (function outerFunction(currentThreadName) {\n" +
" function innerFunction() {\n" +
" return currentThreadName;\n" +
" }\n" +
" return innerFunction;\n" +
" })(java.lang.Thread.currentThread().toString())\n");
ScriptObjectMirror jsFunction = (ScriptObjectMirror) engine.eval(testJsFunction);
IntConsumer invokeAndTest = i-> {
Object received = jsFunction.call(jsFunction);
Assert.assertEquals(Thread.currentThread().toString(), received);
};
IntStream.range(0, 10).parallel().forEach(invokeAndTest);
}
@Test
public void testClosureMutation() throws ScriptException, InterruptedException {
Map<String, String> mutableParameter = new HashMap<>();
mutableParameter.put("value", "foo");
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
String testJsFunction = (
"(function outerFunction(mutable) {\n" +
" function innerFunction() {\n" +
" return mutable.value;\n" +
" }\n" +
" return innerFunction;\n" +
"})\n");
ScriptObjectMirror outerFunction = (ScriptObjectMirror) engine.eval(testJsFunction);
ScriptObjectMirror innerFunction = (ScriptObjectMirror) outerFunction.call(outerFunction, mutableParameter);
// verify function works as expected
Assert.assertEquals("foo", innerFunction.call(innerFunction));
// mutate closure
mutableParameter.put("value", "bar");
// call inner function again
Assert.assertEquals("bar", innerFunction.call(innerFunction));
}
@Test
public void testClosureMutationInOtherThread() throws ScriptException, InterruptedException {
String currentThreadName = Thread.currentThread().toString();
Map<String, String> mutableParameter = new HashMap<>();
mutableParameter.put("value", currentThreadName);
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
String testJsFunction = (
"(function outerFunction(mutable) {\n" +
" function innerFunction() {\n" +
" return mutable.value;\n" +
" }\n" +
" return innerFunction;\n" +
"})\n");
ScriptObjectMirror outerFunction = (ScriptObjectMirror) engine.eval(testJsFunction);
ScriptObjectMirror innerFunction = (ScriptObjectMirror) outerFunction.call(outerFunction, mutableParameter);
// verify function works as expected
Assert.assertEquals(currentThreadName, innerFunction.call(innerFunction));
// mutate closure in other thread
Thread thread = new Thread(() -> mutableParameter.put("value", Thread.currentThread().toString()));
thread.start();
thread.join();
// call inner function again, verify it does not return currentThreadName anymore
Assert.assertNotEquals(currentThreadName, innerFunction.call(innerFunction));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment