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

kenhuuu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 9bc27d1d095b7daa8e7ce896ce2468f67bb67b45
Author: Ken Hu <[email protected]>
AuthorDate: Tue Jun 9 10:04:54 2026 -0700

    Improve error for divergent transaction "g" value CTR
    
    Assisted-by: Claude Code:claude-opus-4-6
---
 .../server/handler/HttpGremlinEndpointHandler.java    | 11 ++++++++++-
 .../server/transaction/TransactionManager.java        |  5 +++--
 .../server/transaction/UnmanagedTransaction.java      | 11 +++++++++++
 .../tinkerpop/gremlin/server/util/GremlinError.java   | 15 +++++++++++++++
 .../GremlinServerHttpTransactionIntegrateTest.java    | 19 ++++++++++++++-----
 5 files changed, 53 insertions(+), 8 deletions(-)

diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
index 8bc6e79c3e..0c64aca813 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/HttpGremlinEndpointHandler.java
@@ -228,10 +228,19 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
 
                     doBegin(requestCtx);
                 } else if (txId != null) {
+                    final String requestedSource = 
requestMessage.getField(Tokens.ARGS_G);
+                    final Optional<UnmanagedTransaction> txOpt = 
transactionManager.get(txId);
+                    if (txOpt.isEmpty()) {
+                        throw new 
ProcessingException(GremlinError.transactionNotFound(txId));
+                    }
+                    final UnmanagedTransaction tx = txOpt.get();
+                    if (!tx.getTraversalSourceName().equals(requestedSource)) {
+                        throw new 
ProcessingException(GremlinError.traversalSourceMismatch(tx.getTraversalSourceName(),
 requestedSource));
+                    }
                     // This check makes sure that the underlying Graph is 
already open to stop a closed transaction
                     // from re-opening due to the default autostart nature of 
transactions. This occurs in cases where a
                     // transactional traversal is submitted after a 
commit/rollback.
-                    final Graph g = 
graphManager.getTraversalSource(requestMessage.getField(Tokens.ARGS_G)).getGraph();
+                    final Graph g = 
graphManager.getTraversalSource(requestedSource).getGraph();
                     if ((!g.tx().isOpen())) {
                         throw new 
ProcessingException(GremlinError.transactionNotFound(txId));
                     }
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/transaction/TransactionManager.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/transaction/TransactionManager.java
index 6c4e8b0f76..6901664c15 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/transaction/TransactionManager.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/transaction/TransactionManager.java
@@ -93,7 +93,7 @@ public class TransactionManager {
             throw Graph.Exceptions.transactionsNotSupported();
         }
 
-        final UnmanagedTransaction txCtx = 
createTransactionContext(ts.getGraph());
+        final UnmanagedTransaction txCtx = 
createTransactionContext(traversalSourceName, ts.getGraph());
         logger.debug("Transaction {} created for source {}", 
txCtx.getTransactionId(), traversalSourceName);
         return txCtx;
     }
@@ -111,7 +111,7 @@ public class TransactionManager {
      * Creates a unique transaction ID, retrying on the unlikely UUID 
collision. The newly created
      * {@link UnmanagedTransaction} is inserted into the transactions map.
      */
-    private UnmanagedTransaction createTransactionContext(final Graph graph) {
+    private UnmanagedTransaction createTransactionContext(final String 
traversalSourceName, final Graph graph) {
         String txId;
         UnmanagedTransaction ctx;
 
@@ -120,6 +120,7 @@ public class TransactionManager {
             ctx = new UnmanagedTransaction(
                     txId,
                     this,
+                    traversalSourceName,
                     graph,
                     scheduledExecutorService,
                     transactionTimeoutMs,
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/transaction/UnmanagedTransaction.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/transaction/UnmanagedTransaction.java
index 0b98b9b846..c3d3b8a274 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/transaction/UnmanagedTransaction.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/transaction/UnmanagedTransaction.java
@@ -47,6 +47,7 @@ public class UnmanagedTransaction {
     private static final Logger logger = 
LoggerFactory.getLogger(UnmanagedTransaction.class);
 
     private final String transactionId;
+    private final String traversalSourceName;
     private final TransactionManager manager;
     private final Graph graph;
     private final ScheduledExecutorService scheduledExecutorService;
@@ -66,18 +67,21 @@ public class UnmanagedTransaction {
      *
      * @param transactionId The unique identifier for this transaction
      * @param transactionManager The manager that owns this transaction's 
lifecycle
+     * @param traversalSourceName The traversal source name bound at begin time
      * @param graph The graph instance for this transaction
      * @param scheduledExecutorService Scheduler for timeout management
      * @param transactionTimeout Timeout in milliseconds before auto-rollback
      */
     public UnmanagedTransaction(final String transactionId,
                                 final TransactionManager transactionManager,
+                                final String traversalSourceName,
                                 final Graph graph,
                                 final ScheduledExecutorService 
scheduledExecutorService,
                                 final long transactionTimeout,
                                 final long perGraphClose) {
         logger.debug("New transaction context established for {}", 
transactionId);
         this.transactionId = transactionId;
+        this.traversalSourceName = traversalSourceName;
         this.manager = transactionManager;
         this.graph = graph;
         this.scheduledExecutorService = scheduledExecutorService;
@@ -96,6 +100,13 @@ public class UnmanagedTransaction {
         return transactionId;
     }
 
+    /**
+     * Returns the traversal source name bound at begin time.
+     */
+    public String getTraversalSourceName() {
+        return traversalSourceName;
+    }
+
     /**
      * Resets the timeout for this transaction. Called on each request.
      */
diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java
 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java
index cbdfa36de8..e0e74c26d0 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/GremlinError.java
@@ -250,4 +250,19 @@ public class GremlinError {
         final String message = String.format("Script engine [%s] is not 
available.", language);
         return new GremlinError(HttpResponseStatus.BAD_REQUEST, message, 
"InvalidRequestException");
     }
+
+    /**
+     * Creates an error for when a transactional request uses a different 
traversal source
+     * name than the one bound at begin time.
+     *
+     * @param expected The traversal source name bound at transaction begin
+     * @param actual The traversal source name in the current request
+     * @return A GremlinError with appropriate message and status code
+     */
+    public static GremlinError traversalSourceMismatch(final String expected, 
final String actual) {
+        final String message = String.format(
+                "Transaction traversal source alias mismatch: transaction 
bound to '%s' but request used '%s'",
+                expected, actual);
+        return new GremlinError(HttpResponseStatus.BAD_REQUEST, message, 
"TransactionException");
+    }
 }
diff --git 
a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpTransactionIntegrateTest.java
 
b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpTransactionIntegrateTest.java
index 8aa919cad8..156b2773dd 100644
--- 
a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpTransactionIntegrateTest.java
+++ 
b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerHttpTransactionIntegrateTest.java
@@ -105,6 +105,15 @@ public class GremlinServerHttpTransactionIntegrateTest 
extends AbstractGremlinSe
             case "shouldRollbackAbandonedTransaction":
                 settings.transactionTimeout = 300;
                 break;
+            case "shouldRejectMismatchedGraphAliasInTransaction": {
+                final Settings.GraphSettings gs = new Settings.GraphSettings();
+                gs.configuration = 
"conf/tinkertransactiongraph-empty.properties";
+                final Settings.TraversalSourceSettings ts = new 
Settings.TraversalSourceSettings();
+                ts.name = "gTxEmpty";
+                gs.traversalSources.add(ts);
+                settings.graphs.put("tx2", gs);
+                break;
+            }
         }
         return settings;
     }
@@ -517,11 +526,11 @@ public class GremlinServerHttpTransactionIntegrateTest 
extends AbstractGremlinSe
     public void shouldRejectMismatchedGraphAliasInTransaction() throws 
Exception {
         final String txId = beginTx(client, GTX);
 
-        // send a request with the same txId but a different graph alias
-        try (final CloseableHttpResponse r = submitInTx(client, txId, 
"g.V().count()", "gclassic")) {
-            final int status = r.getStatusLine().getStatusCode();
-            assertTrue("Expected error status for alias mismatch, got " + 
status,
-                    status == 400 || status == 404 || status == 500);
+        // send a request with the same txId but a different transactional 
graph alias
+        try (final CloseableHttpResponse r = submitInTx(client, txId, 
"g.V().count()", "gTxEmpty")) {
+            assertEquals(400, r.getStatusLine().getStatusCode());
+            final String msg = extractStatusMessage(r);
+            assertTrue(msg.contains("alias"));
         }
     }
 

Reply via email to