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 d43ef50b59b4a2daf488d108f4f6f4eb9b98fadd
Author: Ken Hu <[email protected]>
AuthorDate: Tue Jun 9 20:06:54 2026 -0700

    Centralize transaction request classification in Context CTR
    
    The match is now case-sensitive to stay consistent with how the grammar 
parses the same string.
    
    Assisted-by: Claude Code:claude-opus-4-8
---
 .../apache/tinkerpop/gremlin/server/Context.java   | 58 +++++++++++++++++++++-
 .../server/handler/HttpGremlinEndpointHandler.java | 37 +++-----------
 2 files changed, 63 insertions(+), 32 deletions(-)

diff --git 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java
index 10148abe08..c7c5a610e3 100644
--- 
a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java
+++ 
b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java
@@ -53,6 +53,7 @@ public class Context {
     private final long requestTimeout;
     private final String materializeProperties;
     private final Object gremlinArgument;
+    private final RequestType requestType;
     private HttpGremlinEndpointHandler.RequestState requestState;
     private final AtomicBoolean startedResponse = new AtomicBoolean(false);
     private ScheduledFuture<?> timeoutExecutor = null;
@@ -80,7 +81,9 @@ public class Context {
         this.scheduledExecutorService = scheduledExecutorService;
 
         // order of calls matter as one depends on the next
-        this.gremlinArgument = requestMessage.getGremlin();
+        final String gremlin = requestMessage.getGremlin();
+        this.gremlinArgument = gremlin;
+        this.requestType = RequestType.fromGremlin(gremlin);
         this.requestState = requestState;
         this.requestTimeout = determineTimeout();
         this.materializeProperties = determineMaterializeProperties();
@@ -128,6 +131,27 @@ public class Context {
         return transactionId;
     }
 
+    /**
+     * Returns {@code true} if this request is a transaction begin control 
command.
+     */
+    public boolean isTransactionBegin() {
+        return requestType == RequestType.BEGIN_TX;
+    }
+
+    /**
+     * Returns {@code true} if this request is a transaction commit control 
command.
+     */
+    public boolean isTransactionCommit() {
+        return requestType == RequestType.COMMIT_TX;
+    }
+
+    /**
+     * Returns {@code true} if this request is a transaction rollback control 
command.
+     */
+    public boolean isTransactionRollback() {
+        return requestType == RequestType.ROLLBACK_TX;
+    }
+
     public void setTransactionId(final String transactionId) {
         this.transactionId = transactionId;
     }
@@ -233,4 +257,36 @@ public class Context {
     public void setRequestState(HttpGremlinEndpointHandler.RequestState 
requestState) {
         this.requestState = requestState;
     }
+
+    /**
+     * Classifies an HTTP request by the kind of work it performs. Transaction 
control requests reuse the canonical
+     * Gremlin idioms ({@code g.tx().begin()} etc.) as protocol signals rather 
than evaluating them, because the
+     * ThreadLocal-bound nature of graph transactions forces the server to 
route the underlying graph operation onto
+     * a dedicated per-transaction thread instead of the shared eval path. The 
match is therefore against fixed,
+     * case-sensitive tokens — the same spelling the script engine would 
accept — not a grammar parse.
+     */
+    private enum RequestType {
+        BEGIN_TX,
+        COMMIT_TX,
+        ROLLBACK_TX,
+        EVAL;
+
+        private static final String BEGIN = "g.tx().begin()";
+        private static final String COMMIT = "g.tx().commit()";
+        private static final String ROLLBACK = "g.tx().rollback()";
+
+        /**
+         * Classifies a gremlin string into a {@link RequestType}. 
Leading/trailing whitespace is tolerated, but the
+         * token match is case-sensitive so that the routing decision cannot 
disagree with how the script engine would
+         * parse the same string.
+         */
+        private static RequestType fromGremlin(final String gremlin) {
+            if (gremlin == null) return EVAL;
+            final String trimmed = gremlin.trim();
+            if (trimmed.equals(BEGIN)) return BEGIN_TX;
+            if (trimmed.equals(COMMIT)) return COMMIT_TX;
+            if (trimmed.equals(ROLLBACK)) return ROLLBACK_TX;
+            return EVAL;
+        }
+    }
 }
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 390dd85d05..40f0d2b79f 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
@@ -218,8 +218,7 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
                 // These guards prevent any obvious failures from returning 
200 OK early by detecting them here and
                 // throwing before any other processing starts so the user 
gets a better error code.
                 final String txId = requestCtx.getTransactionId();
-                final String gremlin = requestMessage.getGremlin();
-                if (isTransactionBegin(gremlin)) {
+                if (requestCtx.isTransactionBegin()) {
                     // If this is a begin transaction request then we need to 
create the Transaction ID first since the
                     // dual-transmission expectation means the response header 
below should contain it.
 
@@ -244,7 +243,7 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
                     if ((!g.tx().isOpen())) {
                         throw new 
ProcessingException(GremlinError.transactionNotFound(txId));
                     }
-                } else if ((txId == null) && (isTransactionCommit(gremlin) || 
isTransactionRollback(gremlin))) {
+                } else if ((txId == null) && (requestCtx.isTransactionCommit() 
|| requestCtx.isTransactionRollback())) {
                     // Logically, commit/rollback should only be allowed on a 
transactional request.
                     throw new 
ProcessingException(GremlinError.transactionalControlRequiresTransaction());
                 }
@@ -309,7 +308,7 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
         });
 
         try {
-            final boolean isBeginTransactionRequest = 
isTransactionBegin(requestMessage.getGremlin());
+            final boolean isBeginTransactionRequest = 
requestCtx.isTransactionBegin();
             final Future<?> executionFuture = ((requestCtx.getTransactionId() 
!= null) && !isBeginTransactionRequest) ?
                     
transactionManager.get(requestCtx.getTransactionId()).get().submit(evalFuture) :
                     
requestCtx.getGremlinExecutor().getExecutorService().submit(evalFuture);
@@ -355,11 +354,11 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
         // Early guard against fake or incorrect transaction IDs.
         if ((txId != null) && transaction.isEmpty()) throw new 
ProcessingException(GremlinError.transactionNotFound(txId));
 
-        if (isTransactionBegin(request.getGremlin())) {
+        if (requestContext.isTransactionBegin()) {
             runBegin(requestContext, transaction.get(), serializer);
-        } else if (isTransactionCommit(request.getGremlin())) {
+        } else if (requestContext.isTransactionCommit()) {
             handleGraphOp(requestContext, txId, Transaction::commit, 
serializer);
-        } else if 
(isTransactionRollback(requestContext.getRequestMessage().getGremlin())) {
+        } else if (requestContext.isTransactionRollback()) {
             handleGraphOp(requestContext, txId, Transaction::rollback, 
serializer);
         } else {
             // Both transactional and non-transactional traversals follow this 
path for response chunking.
@@ -491,30 +490,6 @@ public class HttpGremlinEndpointHandler extends 
SimpleChannelInboundHandler<Requ
         }
     }
 
-    /**
-     * Detects if the gremlin script is a transaction begin command.
-     */
-    private boolean isTransactionBegin(final String gremlin) {
-        if (gremlin == null) return false;
-        return gremlin.trim().equalsIgnoreCase("g.tx().begin()");
-    }
-
-    /**
-     * Detects if the gremlin script is a transaction commit command.
-     */
-    private boolean isTransactionCommit(final String gremlin) {
-        if (gremlin == null) return false;
-        return gremlin.trim().equalsIgnoreCase("g.tx().commit()");
-    }
-
-    /**
-     * Detects if the gremlin script is a transaction rollback command.
-     */
-    private boolean isTransactionRollback(final String gremlin) {
-        if (gremlin == null) return false;
-        return gremlin.trim().equalsIgnoreCase("g.tx().rollback()");
-    }
-
     /**
      * Handle begin by creating an {@link UnmanagedTransaction} and submitting 
the open to its executor.
      */

Reply via email to