Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
[[release-3-7-6]]
=== TinkerPop 3.7.6 (NOT OFFICIALLY RELEASED YET)
* Integrated Python driver examples into automated build process to ensure examples remain functional.
* Added `closeSessionPostGraphOp` to the Gremlin Server settings to indicate that the `Session` should be closed on either a successful commit or rollback.
[[release-3-7-5]]
=== TinkerPop 3.7.5 (Release Date: November 12, 2025)
Expand Down
1 change: 1 addition & 0 deletions docs/src/reference/gremlin-applications.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,7 @@ The following table describes the various YAML configuration options that Gremli
|authorization.authorizer |The fully qualified classname of an `Authorizer` implementation to use. |_none_
|authorization.config |A `Map` of configuration settings to be passed to the `Authorizer` when it is constructed. The settings available are dependent on the implementation. |_none_
|channelizer |The fully qualified classname of the `Channelizer` implementation to use. A `Channelizer` is a "channel initializer" which Gremlin Server uses to define the type of processing pipeline to use. By allowing different `Channelizer` implementations, Gremlin Server can support different communication protocols (e.g. WebSocket). |`WebSocketChannelizer`
|closeSessionPostGraphOp |Controls whether a `Session` will be closed by the server after a successful TX_COMMIT or TX_ROLLBACK bytecode request. |_false_
|enableAuditLog |The `AuthenticationHandler`, `AuthorizationHandler` and processors can issue audit logging messages with the authenticated user, remote socket address and requests with a gremlin query. For privacy reasons, the default value of this setting is false. The audit logging messages are logged at the INFO level via the `audit.org.apache.tinkerpop.gremlin.server` logger, which can be configured using the `logback.xml` file. |_false_
|graphManager |The fully qualified classname of the `GraphManager` implementation to use. A `GraphManager` is a class that adheres to the TinkerPop `GraphManager` interface, allowing custom implementations for storing and managing graph references, as well as defining custom methods to open and close graphs instantiations. To prevent Gremlin Server from starting when all graphs fails, the `CheckedGraphManager` can be used.|`DefaultGraphManager`
|graphs |A `Map` of `Graph` configuration files where the key of the `Map` becomes the name to which the `Graph` will be bound and the value is the file name of a `Graph` configuration file. |_none_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ public Settings() {
*/
public boolean strictTransactionManagement = false;

/**
* If set to {@code true} the Gremlin Server will close the session when a GraphOp (commit or rollback) is
* successfully completed on that session.
*
* NOTE: Defaults to false in 3.7.x/3.8.x to prevent breaking change.
*/
public boolean closeSessionPostGraphOp = false;

/**
* The full class name of the {@link Channelizer} to use in Gremlin Server.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,10 @@ protected void handleGraphOperation(final SessionTask sessionTask, final Bytecod
.code(ResponseStatusCode.NO_CONTENT)
.statusAttributes(attributes)
.create());

if (sessionTask.getSettings().closeSessionPostGraphOp) {
close();
}
} else {
throw new IllegalStateException(String.format(
"Bytecode in request is not a recognized graph operation: %s", bytecode.toString()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ protected void handleIterator(final Context context, final Iterator itty) throws
int warnCounter = 0;

// sessionless requests are always transaction managed, but in-session requests are configurable.
final boolean managedTransactionsForRequest = manageTransactions ?
true : (Boolean) msg.getArgs().getOrDefault(Tokens.ARGS_MANAGE_TRANSACTION, false);
final boolean managedTransactionsForRequest = shouldManageTransactionsForRequest(context);

// we have an empty iterator - happens on stuff like: g.V().iterate()
if (!itty.hasNext()) {
Expand Down Expand Up @@ -371,4 +370,9 @@ protected static void attemptRollback(final RequestMessage msg, final GraphManag
graphManager.rollbackAll();
}
}

protected boolean shouldManageTransactionsForRequest(final Context ctx) {
return manageTransactions ?
true : (Boolean) ctx.getRequestMessage().getArgs().getOrDefault(Tokens.ARGS_MANAGE_TRANSACTION, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ public class SessionOpProcessor extends AbstractEvalOpProcessor {
}};
}

// Determines whether to close the session after a successful COMMIT/ROLLBACK. Set during init().
private boolean closeSessionPostGraphOp;

public SessionOpProcessor() {
super(false);
}
Expand All @@ -154,6 +157,7 @@ public String getName() {
public void init(final Settings settings) {
this.maxParameters = (int) settings.optionalProcessor(SessionOpProcessor.class).orElse(DEFAULT_SETTINGS).config.
getOrDefault(CONFIG_MAX_PARAMETERS, DEFAULT_MAX_PARAMETERS);
this.closeSessionPostGraphOp = settings.closeSessionPostGraphOp;
}

/**
Expand Down Expand Up @@ -546,6 +550,13 @@ protected void handleGraphOperation(final Bytecode bytecode, final Graph graph,
.statusAttributes(attributes)
.create());

if (closeSessionPostGraphOp) {
// Setting force to true prevents deadlock when this thread attempts to destroy the session.
// This should be safe since either a commit or rollback just finished so the transaction
// shouldn't be open.
session.manualKill(true);
}

} catch (Throwable t) {
onError(graph, context);
// if any exception in the chain is TemporaryException or Failure then we should respond with the
Expand All @@ -571,6 +582,13 @@ protected void handleGraphOperation(final Bytecode bytecode, final Graph graph,
.statusMessage(t.getMessage())
.statusAttributeException(t).create());
}

if (closeSessionPostGraphOp && shouldManageTransactionsForRequest(context)) {
// Destroy the session after a successful rollback due to error. Placed here rather than
// in a finally block since we don't want to end the session if no commit/rollback succeeded.
session.manualKill(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is in a catch block that is catching all error types, is it possible for session.manualKill to be called twice if the previous call in the try block threw an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't happen, but even if it does, theres explicit handling in that method for that situation so it's safe to call it more than once.

}

if (t instanceof Error) {
//Re-throw any errors to be handled by and set as the result the FutureTask
throw t;
Expand All @@ -589,20 +607,17 @@ protected void handleGraphOperation(final Bytecode bytecode, final Graph graph,
}

protected void beforeProcessing(final Graph graph, final Context ctx) {
final boolean managedTransactionsForRequest = manageTransactions ?
true : (Boolean) ctx.getRequestMessage().getArgs().getOrDefault(Tokens.ARGS_MANAGE_TRANSACTION, false);
final boolean managedTransactionsForRequest = shouldManageTransactionsForRequest(ctx);
if (managedTransactionsForRequest && graph.features().graph().supportsTransactions() && graph.tx().isOpen()) graph.tx().rollback();
}

protected void onError(final Graph graph, final Context ctx) {
final boolean managedTransactionsForRequest = manageTransactions ?
true : (Boolean) ctx.getRequestMessage().getArgs().getOrDefault(Tokens.ARGS_MANAGE_TRANSACTION, false);
final boolean managedTransactionsForRequest = shouldManageTransactionsForRequest(ctx);
if (managedTransactionsForRequest && graph.features().graph().supportsTransactions() && graph.tx().isOpen()) graph.tx().rollback();
}

protected void onTraversalSuccess(final Graph graph, final Context ctx) {
final boolean managedTransactionsForRequest = manageTransactions ?
true : (Boolean) ctx.getRequestMessage().getArgs().getOrDefault(Tokens.ARGS_MANAGE_TRANSACTION, false);
final boolean managedTransactionsForRequest = shouldManageTransactionsForRequest(ctx);
if (managedTransactionsForRequest && graph.features().graph().supportsTransactions() && graph.tx().isOpen()) graph.tx().commit();
}

Expand Down
Loading
Loading