This is an automated email from the ASF dual-hosted git repository.

colegreer pushed a commit to branch 3.7-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


The following commit(s) were added to refs/heads/3.7-dev by this push:
     new e54ae50e4c TINKERPOP-3210 Fix cap() step mid-traversal in OLAP (#3332)
e54ae50e4c is described below

commit e54ae50e4c94db7d76692fc27314bbc59adf2802
Author: Stephen Mallette <[email protected]>
AuthorDate: Mon Mar 23 20:03:13 2026 -0400

    TINKERPOP-3210 Fix cap() step mid-traversal in OLAP (#3332)
    
    Added check to memory.set() to ensure masterState() in 
MemoryTraversalSideEffects. Changed TraversalVertexProgram to gather all 
completed barriers across all rounds which prevented stale lazy re-evaluation.
---
 CHANGELOG.asciidoc                                 |  1 +
 .../traversal/MemoryTraversalSideEffects.java      |  7 ++-
 .../computer/traversal/TraversalVertexProgram.java |  9 ++-
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  3 +
 gremlin-go/driver/cucumber/gremlin.go              |  3 +
 .../gremlin-javascript/test/cucumber/gremlin.js    |  2 +
 .../src/main/python/tests/feature/gremlin.py       |  2 +
 .../test/features/sideEffect/Aggregate.feature     | 66 ++++++++++++++++++++++
 8 files changed, 90 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index f1ff7a13f8..5b5558d1c3 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -35,6 +35,7 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Added `SessionedChildClient` that borrows connections from a different 
`Client` for use with `Sessions`.
 * Added `reuseConnectionsForSessions` to Java GLV settings to decide whether 
to use `SessionedChildClient` for remote transactions.
 * Added support for Node 22 and 24 alongside Node 20.
+* Fixed `cap()` step throwing an error when used mid-traversal in OLAP.
 
 [[release-3-7-5]]
 === TinkerPop 3.7.5 (Release Date: November 12, 2025)
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/MemoryTraversalSideEffects.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/MemoryTraversalSideEffects.java
index 9bc91ad69e..9d180b4ec8 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/MemoryTraversalSideEffects.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/MemoryTraversalSideEffects.java
@@ -57,7 +57,12 @@ public final class MemoryTraversalSideEffects implements 
TraversalSideEffects {
     @Override
     public void set(final String key, final Object value) {
         this.sideEffects.set(key, value);
-        if (null != this.memory)
+
+        // looks like calls to this method are only permitted during 
setup/terminate (i.e. masterState)
+        // during worker execution (e.g. cap() firing lazily via a downstream 
local step), skip the
+        // memory write to avoid IllegalArgumentException from the distributed 
memory implementation.
+        // see TINKERPOP-3210 for an example of how this fails.
+        if (null != this.memory && this.phase.masterState())
             this.memory.set(key, value);
     }
 
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/TraversalVertexProgram.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/TraversalVertexProgram.java
index 0273302e9b..c247fb26e3 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/TraversalVertexProgram.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/computer/traversal/TraversalVertexProgram.java
@@ -329,8 +329,13 @@ public final class TraversalVertexProgram implements 
VertexProgram<TraverserSet<
             MasterExecutor.processMemory(this.traversalMatrix, memory, 
toProcessTraversers, completedBarriers);
             // process all results from barriers locally and when elements are 
touched, put them in remoteActiveTraversers
             MasterExecutor.processTraversers(this.traversal, 
this.traversalMatrix, toProcessTraversers, remoteActiveTraversers, 
haltedTraversers, this.haltedTraverserStrategy);
-            // tell parallel barriers that might not have been active in the 
last round that they are no longer active
-            memory.set(COMPLETED_BARRIERS, completedBarriers);
+            // tell parallel barriers that might not have been active in the 
last round that they are no longer active.
+            // accumulate all previously-completed barriers: worker clones 
start with done=false and need done() called
+            // for every barrier ever completed (not just the most recent 
ones) to prevent stale lazy re-evaluation.
+            // see TINKERPOP-3210 for the lazy cap() re-firing that motivated 
this change.
+            final Set<String> allCompletedBarriers = new 
HashSet<>(memory.get(COMPLETED_BARRIERS));
+            allCompletedBarriers.addAll(completedBarriers);
+            memory.set(COMPLETED_BARRIERS, allCompletedBarriers);
             if (!remoteActiveTraversers.isEmpty() ||
                     
completedBarriers.stream().map(this.traversalMatrix::getStepById).filter(step 
-> step instanceof LocalBarrier).findAny().isPresent()) {
                 // send active traversers back to workers
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index e483a0a6b6..739b6a04fd 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -1483,6 +1483,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                
{"g_withSideEffectXa_xx1_addAllX_V_aggregateXlocal_aX_byXageX_capXaX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.WithSideEffect("a",p["xx1"],Operator.AddAll).V().Aggregate(Scope.Local,"a").By("age").Cap<object>("a")}},
 
                
{"g_withSideEffectXa_xx1_assignX_V_aggregateXaX_byXageX_capXaX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.WithSideEffect("a",p["xx1"],Operator.Assign).V().Aggregate("a").By("age").Cap<object>("a")}},
 
                
{"g_withSideEffectXa_xx1_assignX_V_order_byXageX_aggregateXlocal_aX_byXageX_capXaX",
 new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.WithSideEffect("a",p["xx1"],Operator.Assign).V().Order().By("age").Aggregate(Scope.Local,"a").By("age").Cap<object>("a")}},
 
+               {"g_V_repeatXaggregateXaXX_timesX2X_capXaX_unfold", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.V().Repeat(__.Aggregate("a")).Times(2).Cap<object>("a").Unfold<object>()}}, 
+               {"g_V_aggregateXaX_capXaX_unfold_both", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.V().Aggregate("a").Cap<object>("a").Unfold<object>().Both()}}, 
+               {"g_V_aggregateXaX_capXaX_unfold_barrier_both", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.V().Aggregate("a").Cap<object>("a").Unfold<object>().Barrier().Both()}}, 
                {"g_V_fail", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fail()}}, 
                {"g_V_failXmsgX", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fail("msg")}}, 
                {"g_V_unionXout_failX", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.V().Union<object>(__.Out(),__.Fail())}}, 
diff --git a/gremlin-go/driver/cucumber/gremlin.go 
b/gremlin-go/driver/cucumber/gremlin.go
index 71a5ba599a..bd619f592a 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -1454,6 +1454,9 @@ var translationMap = map[string][]func(g 
*gremlingo.GraphTraversalSource, p map[
     "g_withSideEffectXa_xx1_addAllX_V_aggregateXlocal_aX_byXageX_capXaX": 
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.WithSideEffect("a", p["xx1"], 
gremlingo.Operator.AddAll).V().Aggregate(gremlingo.Scope.Local, 
"a").By("age").Cap("a")}}, 
     "g_withSideEffectXa_xx1_assignX_V_aggregateXaX_byXageX_capXaX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.WithSideEffect("a", p["xx1"], 
gremlingo.Operator.Assign).V().Aggregate("a").By("age").Cap("a")}}, 
     
"g_withSideEffectXa_xx1_assignX_V_order_byXageX_aggregateXlocal_aX_byXageX_capXaX":
 {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.WithSideEffect("a", p["xx1"], 
gremlingo.Operator.Assign).V().Order().By("age").Aggregate(gremlingo.Scope.Local,
 "a").By("age").Cap("a")}}, 
+    "g_V_repeatXaggregateXaXX_timesX2X_capXaX_unfold": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.V().Repeat(gremlingo.T__.Aggregate("a")).Times(2).Cap("a").Unfold()}}, 
+    "g_V_aggregateXaX_capXaX_unfold_both": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.V().Aggregate("a").Cap("a").Unfold().Both()}}, 
+    "g_V_aggregateXaX_capXaX_unfold_barrier_both": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.V().Aggregate("a").Cap("a").Unfold().Barrier().Both()}}, 
     "g_V_fail": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Fail()}}, 
     "g_V_failXmsgX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Fail("msg")}}, 
     "g_V_unionXout_failX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.V().Union(gremlingo.T__.Out(), gremlingo.T__.Fail())}}, 
diff --git 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index 1403e37d53..58da9767c5 100644
--- 
a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ 
b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -1474,6 +1474,8 @@ const gremlins = {
     g_withSideEffectXa_xx1_addAllX_V_aggregateXlocal_aX_byXageX_capXaX: 
[function({g, xx1}) { return 
g.withSideEffect("a",xx1,Operator.addAll).V().aggregate(Scope.local,"a").by("age").cap("a")
 }], 
     g_withSideEffectXa_xx1_assignX_V_aggregateXaX_byXageX_capXaX: 
[function({g, xx1}) { return 
g.withSideEffect("a",xx1,Operator.assign).V().aggregate("a").by("age").cap("a") 
}], 
     
g_withSideEffectXa_xx1_assignX_V_order_byXageX_aggregateXlocal_aX_byXageX_capXaX:
 [function({g, xx1}) { return 
g.withSideEffect("a",xx1,Operator.assign).V().order().by("age").aggregate(Scope.local,"a").by("age").cap("a")
 }], 
+    g_V_aggregateXaX_capXaX_unfold_both: [function({g}) { return 
g.V().aggregate("a").cap("a").unfold().both() }], 
+    g_V_aggregateXaX_capXaX_unfold_barrier_both: [function({g}) { return 
g.V().aggregate("a").cap("a").unfold().barrier().both() }], 
     g_V_fail: [function({g}) { return g.V().fail() }], 
     g_V_failXmsgX: [function({g}) { return g.V().fail("msg") }], 
     g_V_unionXout_failX: [function({g}) { return 
g.V().union(__.out(),__.fail()) }], 
diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py 
b/gremlin-python/src/main/python/tests/feature/gremlin.py
index a543860a70..ed40a40218 100644
--- a/gremlin-python/src/main/python/tests/feature/gremlin.py
+++ b/gremlin-python/src/main/python/tests/feature/gremlin.py
@@ -1456,6 +1456,8 @@ world.gremlins = {
     'g_withSideEffectXa_xx1_addAllX_V_aggregateXlocal_aX_byXageX_capXaX': 
[(lambda g, 
xx1=None:g.with_side_effect('a',xx1,Operator.add_all).V().aggregate(Scope.local,'a').by('age').cap('a'))],
 
     'g_withSideEffectXa_xx1_assignX_V_aggregateXaX_byXageX_capXaX': [(lambda 
g, 
xx1=None:g.with_side_effect('a',xx1,Operator.assign).V().aggregate('a').by('age').cap('a'))],
 
     
'g_withSideEffectXa_xx1_assignX_V_order_byXageX_aggregateXlocal_aX_byXageX_capXaX':
 [(lambda g, 
xx1=None:g.with_side_effect('a',xx1,Operator.assign).V().order().by('age').aggregate(Scope.local,'a').by('age').cap('a'))],
 
+    'g_V_aggregateXaX_capXaX_unfold_both': [(lambda 
g:g.V().aggregate('a').cap('a').unfold().both())], 
+    'g_V_aggregateXaX_capXaX_unfold_barrier_both': [(lambda 
g:g.V().aggregate('a').cap('a').unfold().barrier().both())], 
     'g_V_fail': [(lambda g:g.V().fail())], 
     'g_V_failXmsgX': [(lambda g:g.V().fail('msg'))], 
     'g_V_unionXout_failX': [(lambda g:g.V().union(__.out(),__.fail()))], 
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Aggregate.feature
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Aggregate.feature
index 7267bedd5d..7c327d8374 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Aggregate.feature
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/sideEffect/Aggregate.feature
@@ -576,3 +576,69 @@ Feature: Step - aggregate()
     Then the result should be unordered
       | result |
       | d[35].i |
+
+  Scenario: g_V_repeatXaggregateXaXX_timesX2X_capXaX_unfold
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().repeat(__.aggregate("a")).times(2).cap("a").unfold()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |
+      | v[marko] |
+      | v[vadas] |
+      | v[vadas] |
+      | v[lop] |
+      | v[lop] |
+      | v[josh] |
+      | v[josh] |
+      | v[ripple] |
+      | v[ripple] |
+      | v[peter] |
+      | v[peter] |
+
+  Scenario: g_V_aggregateXaX_capXaX_unfold_both
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().aggregate("a").cap("a").unfold().both()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |
+      | v[marko] |
+      | v[marko] |
+      | v[vadas] |
+      | v[josh] |
+      | v[josh] |
+      | v[josh] |
+      | v[lop] |
+      | v[lop] |
+      | v[lop] |
+      | v[peter] |
+      | v[ripple] |
+
+  Scenario: g_V_aggregateXaX_capXaX_unfold_barrier_both
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().aggregate("a").cap("a").unfold().barrier().both()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |
+      | v[marko] |
+      | v[marko] |
+      | v[vadas] |
+      | v[josh] |
+      | v[josh] |
+      | v[josh] |
+      | v[lop] |
+      | v[lop] |
+      | v[lop] |
+      | v[peter] |
+      | v[ripple] |

Reply via email to