Fixed a bug in how global bindings were tracked/applied.

There were several problems here (which ultimately affected the 3.3.0 branch), 
but mainly global bindings were being re-applied to the ScriptContext given to 
eval() and thus making them both engine and global bindings which then led to 
local variables getting assigned to the global scope. Also, getGlobalBindings() 
on GremlinExecutor is no longer necessary and is deprecated as the global 
bindings should be manipulated directly on the GremlinScriptEngine. CTR


Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo
Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/42dc0c7d
Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/42dc0c7d
Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/42dc0c7d

Branch: refs/heads/TINKERPOP-1625
Commit: 42dc0c7de0ad26e3602faebf0c10ee8795bee971
Parents: a052b1f
Author: Stephen Mallette <sp...@genoprime.com>
Authored: Thu Mar 23 02:51:08 2017 -0400
Committer: Stephen Mallette <sp...@genoprime.com>
Committed: Thu Mar 23 02:51:08 2017 -0400

----------------------------------------------------------------------
 .../DefaultGremlinScriptEngineManager.java      |   7 +-
 .../gremlin/groovy/engine/GremlinExecutor.java  |   5 +
 .../groovy/engine/GremlinExecutorTest.java      | 109 ++++++++++++++++++-
 3 files changed, 116 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/42dc0c7d/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/DefaultGremlinScriptEngineManager.java
----------------------------------------------------------------------
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/DefaultGremlinScriptEngineManager.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/DefaultGremlinScriptEngineManager.java
index c8b77e6..09862be 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/DefaultGremlinScriptEngineManager.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/DefaultGremlinScriptEngineManager.java
@@ -478,8 +478,11 @@ public class DefaultGremlinScriptEngineManager implements 
GremlinScriptEngineMan
                 // registered by certain GremlinScriptEngine instances (pretty 
much talking about gremlin-groovy here)
                 // where type checking is made important. this may not be a 
good generic way to handled this in the
                 // long run, but for now we only have two GremlinScriptEngines 
to be concerned about so thus far it
-                // presents no real pains
-                final Object initializedBindings = engine.eval(initScript, 
getBindings());
+                // presents no real pains. global bindings are applied 
automatically to the context via
+                // AbstractScriptEngine.getScriptContext() - passing them 
again here to eval() will just make the
+                // global bindings behave as engine bindings and then you get 
weird things happening (like local vars
+                // becoming global).
+                final Object initializedBindings = engine.eval(initScript);
                 if (initializedBindings != null && initializedBindings 
instanceof Map)
                     ((Map<String,Object>) initializedBindings).forEach((k,v) 
-> put(k,v));
             } catch (Exception ex) {

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/42dc0c7d/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
----------------------------------------------------------------------
diff --git 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
index 8fdb86e..8802793 100644
--- 
a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
+++ 
b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutor.java
@@ -388,6 +388,11 @@ public class GremlinExecutor implements AutoCloseable {
         return scheduledExecutorService;
     }
 
+    /**
+     * @deprecated As of release 3.2.5, replaced by {@link 
#getScriptEngineManager()} to add global scoped bindings
+     * directly to that object.
+     */
+    @Deprecated
     public Bindings getGlobalBindings() {
         return globalBindings;
     }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/42dc0c7d/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutorTest.java
----------------------------------------------------------------------
diff --git 
a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutorTest.java
 
b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutorTest.java
index a56f805..8aed8a6 100644
--- 
a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutorTest.java
+++ 
b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/engine/GremlinExecutorTest.java
@@ -23,6 +23,8 @@ import org.apache.tinkerpop.gremlin.TestHelper;
 import 
org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.ThreadInterruptCustomizerProvider;
 import 
org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.TimedInterruptCustomizerProvider;
 import 
org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.TimedInterruptTimeoutException;
+import org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin;
+import org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin;
 import org.javatuples.Pair;
 import org.junit.Test;
 
@@ -70,6 +72,22 @@ public class GremlinExecutorTest {
     public static Map<String, String> PATHS = new HashMap<>();
     private final BasicThreadFactory testingThreadFactory = new 
BasicThreadFactory.Builder().namingPattern("test-gremlin-executor-%d").build();
 
+    /**
+     * Temporary "useless" plugin definition to force GremlinExecutor to use 
GremlinScriptEngineManager - will be
+     * removed when the old functionality of ScriptEngines is removed.
+     */
+    private final Map<String, Map<String,Object>> triggerPlugin = new 
HashMap<String, Map<String,Object>>() {{
+        put(ImportGremlinPlugin.class.getName(), new HashMap<String,Object>() 
{{
+            put("classImports", Collections.singletonList("java.lang.Math"));
+        }});
+    }};
+
+    private final Map<String, Map<String,Object>> scriptFilePlugin = new 
HashMap<String, Map<String,Object>>() {{
+        put(ScriptFileGremlinPlugin.class.getName(), new 
HashMap<String,Object>() {{
+            put("files", 
Collections.singletonList(PATHS.get("GremlinExecutorInit.groovy")));
+        }});
+    }};
+
     static {
         try {
             final List<String> groovyScriptResources = 
Collections.singletonList("GremlinExecutorInit.groovy");
@@ -213,7 +231,7 @@ public class GremlinExecutorTest {
     }
 
     @Test
-    public void shouldGetGlobalBindings() throws Exception {
+    public void shouldGetGlobalBindingsDeprecated() throws Exception {
         final Bindings b = new SimpleBindings();
         final Object bound = new Object();
         b.put("x", bound);
@@ -223,6 +241,18 @@ public class GremlinExecutorTest {
     }
 
     @Test
+    public void shouldGetGlobalBindings() throws Exception {
+        final Bindings b = new SimpleBindings();
+        final Object bound = new Object();
+        b.put("x", bound);
+        final GremlinExecutor gremlinExecutor = GremlinExecutor.build().
+                addPlugins("gremlin-groovy", triggerPlugin).
+                globalBindings(b).create();
+        assertEquals(bound, 
gremlinExecutor.getScriptEngineManager().getBindings().get("x"));
+        gremlinExecutor.close();
+    }
+
+    @Test
     public void shouldEvalScriptWithGlobalAndLocalBindings() throws Exception {
         final Bindings g = new SimpleBindings();
         g.put("x", 1);
@@ -509,7 +539,7 @@ public class GremlinExecutorTest {
     }
 
     @Test
-    public void shouldInitializeWithScriptAndMakeGlobalBinding() throws 
Exception {
+    public void shouldInitializeWithScriptAndMakeGlobalBindingDeprecated() 
throws Exception {
         final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
                 .addEngineSettings("gremlin-groovy",
                         Collections.emptyList(),
@@ -528,6 +558,21 @@ public class GremlinExecutorTest {
     }
 
     @Test
+    public void shouldInitializeWithScriptAndMakeGlobalBinding() throws 
Exception {
+        final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
+                .addPlugins("gremlin-groovy", scriptFilePlugin)
+                .create();
+
+        assertEquals(2, gremlinExecutor.eval("add(1,1)").get());
+        
assertThat(gremlinExecutor.getScriptEngineManager().getBindings().keySet(), 
contains("name"));
+        
assertThat(gremlinExecutor.getScriptEngineManager().getBindings().keySet(), 
not(hasItem("someSet")));
+
+        assertEquals("stephen", 
gremlinExecutor.getScriptEngineManager().getBindings().get("name"));
+
+        gremlinExecutor.close();
+    }
+
+    @Test
     public void shouldContinueToEvalScriptsEvenWithTimedInterrupt() throws 
Exception {
         final Map<String,List<Object>> compilerCustomizerConfig = new 
HashMap<>();
         final List<Object> args = new ArrayList<>();
@@ -694,7 +739,7 @@ public class GremlinExecutorTest {
     }
 
     @Test
-    public void shouldAllowConcurrentModificationOfGlobals() throws Exception {
+    public void shouldAllowConcurrentModificationOfGlobalsDeprecated() throws 
Exception {
         // this test simulates a scenario that likely shouldn't happen - where 
globals are modified by multiple
         // threads.  globals are created in a synchronized fashion typically 
but it's possible that someone
         // could do something like this and this test validate that 
concurrency exceptions don't occur as a
@@ -748,4 +793,62 @@ public class GremlinExecutorTest {
             assertThat(t.getValue1().get(3).intValue(), greaterThan(-1));
         });
     }
+
+    @Test
+    public void shouldAllowConcurrentModificationOfGlobals() throws Exception {
+        // this test simulates a scenario that likely shouldn't happen - where 
globals are modified by multiple
+        // threads.  globals are created in a synchronized fashion typically 
but it's possible that someone
+        // could do something like this and this test validate that 
concurrency exceptions don't occur as a
+        // result
+        final ExecutorService service = Executors.newFixedThreadPool(8, 
testingThreadFactory);
+        final Bindings globals = new SimpleBindings();
+        globals.put("g", -1);
+        final GremlinExecutor gremlinExecutor = GremlinExecutor.build()
+                .addPlugins("gremlin-groovy", triggerPlugin)
+                .globalBindings(globals).create();
+
+        final AtomicBoolean failed = new AtomicBoolean(false);
+        final int max = 512;
+        final List<Pair<Integer, List<Integer>>> futures = 
Collections.synchronizedList(new ArrayList<>(max));
+        IntStream.range(0, max).forEach(i -> {
+            final int yValue = i * 2;
+            final Bindings b = new SimpleBindings();
+            b.put("x", i);
+            b.put("y", yValue);
+            final int zValue = i * -1;
+
+            final String script = "z=" + zValue + ";[x,y,z,g]";
+            try {
+                service.submit(() -> {
+                    try {
+                        // modify the global in a separate thread
+                        gremlinExecutor.getScriptEngineManager().put("g", i);
+                        
gremlinExecutor.getScriptEngineManager().put(Integer.toString(i), i);
+                        
gremlinExecutor.getScriptEngineManager().getBindings().keySet().stream().filter(s
 -> i % 2 == 0 && !s.equals("g")).findFirst().ifPresent(globals::remove);
+                        final List<Integer> result = (List<Integer>) 
gremlinExecutor.eval(script, b).get();
+                        futures.add(Pair.with(i, result));
+                    } catch (Exception ex) {
+                        failed.set(true);
+                    }
+                });
+            } catch (Exception ex) {
+                throw new RuntimeException(ex);
+            }
+        });
+
+        service.shutdown();
+        assertThat(service.awaitTermination(60000, TimeUnit.MILLISECONDS), 
is(true));
+
+        // likely a concurrency exception if it occurs - and if it does then 
we've messed up because that's what this
+        // test is partially designed to protected against.
+        assertThat(failed.get(), is(false));
+
+        assertEquals(max, futures.size());
+        futures.forEach(t -> {
+            assertEquals(t.getValue0(), t.getValue1().get(0));
+            assertEquals(t.getValue0() * 2, t.getValue1().get(1).intValue());
+            assertEquals(t.getValue0() * -1, t.getValue1().get(2).intValue());
+            assertThat(t.getValue1().get(3).intValue(), greaterThan(-1));
+        });
+    }
 }

Reply via email to