You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2021/02/24 12:22:28 UTC
[tinkerpop] 01/01: TINKERPOP-2517 Added a TemporaryException and
ResponseStatusCode
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-2517
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 8aa5ea4e239c28c155cda93d0d31463e009ec508
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Wed Feb 24 07:19:31 2021 -0500
TINKERPOP-2517 Added a TemporaryException and ResponseStatusCode
Implemented TemporaryException as an interface which means we can't really try-catch it but this approach is probably easier for providers who already have existing exception hierarchies and would just like to be able to hook into Gremlin Server's TemporaryException to ResponseStatusCode.SERVER_ERROR_TEMPORARY mapping and to not introduce breaks into embedded mode implementations where certain hierachies are already expected.
---
CHANGELOG.asciidoc | 1 +
docs/src/dev/provider/index.asciidoc | 14 ++++---
docs/src/reference/gremlin-applications.asciidoc | 8 ++++
docs/src/reference/the-graph.asciidoc | 3 ++
docs/src/upgrade/release-3.5.x.asciidoc | 30 +++++++++++++
.../gremlin/structure/util/TemporaryException.java | 27 ++++++++++++
.../gremlin/driver/message/ResponseStatusCode.java | 41 ++++++++++++++++--
.../gremlin/server/op/AbstractEvalOpProcessor.java | 9 +++-
.../gremlin/server/op/AbstractOpProcessor.java | 13 ++++++
.../server/op/traversal/TraversalOpProcessor.java | 27 ++++++++----
.../server/util/DefaultTemporaryException.java | 49 ++++++++++++++++++++++
.../gremlin/server/GremlinServerIntegrateTest.java | 16 +++++++
12 files changed, 221 insertions(+), 17 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 0ab66ea..111be6a 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -28,6 +28,7 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
* Allowed the possibility for the propagation of `null` as a `Traverser` in Gremlin.
* Fixed a bug where spark-gremlin was not re-attaching properties when using `dedup()`.
* Ensured better consistency of the use of `null` as arguments to mutation steps.
+* Added a `ResponseStatusCode` to indicate that a client should retry its request.
* Prevented `TraversalStrategy` instances from being added more than once, where the new instance replaces the old.
* Allowed `property(T.label,Object)` to be used if no value was supplied to `addV(String)`.
* Allowed additional arguments to `Client.submit()` in Javascript driver to enable setting of parameters like `scriptEvaluationTimeout`.
diff --git a/docs/src/dev/provider/index.asciidoc b/docs/src/dev/provider/index.asciidoc
index aa82021..b83357a 100644
--- a/docs/src/dev/provider/index.asciidoc
+++ b/docs/src/dev/provider/index.asciidoc
@@ -932,14 +932,16 @@ Gremlin Server will send:
|204 |NO CONTENT |The server processed the request but there is no result to return (e.g. an `Iterator` with no elements) - there are no messages remaining in this stream.
|206 |PARTIAL CONTENT |The server successfully returned some content, but there is more in the stream to arrive - wait for a `SUCCESS` to signify the end of the stream.
|401 |UNAUTHORIZED |The request attempted to access resources that the requesting user did not have access to.
+|403 |FORBIDDEN |The server could authenticate the request, but will not fulfill it.
|407 |AUTHENTICATE |A challenge from the server for the client to authenticate its request.
-|497 |CLIENT SERIALIZATION ERROR |The request message contained an object that was not serializable.
-|498 |MALFORMED REQUEST |The request message was not properly formatted which means it could not be parsed at all or the "op" code was not recognized such that Gremlin Server could properly route it for processing. Check the message format and retry the request.
-|499 |INVALID REQUEST ARGUMENTS |The request message was parseable, but the arguments supplied in the message were in conflict or incomplete. Check the message format and retry the request.
+|497 |REQUEST ERROR SERIALIZATION |The request message contained an object that was not serializable.
+|498 |REQUEST ERROR MALFORMED REQUEST |The request message was not properly formatted which means it could not be parsed at all or the "op" code was not recognized such that Gremlin Server could properly route it for processing. Check the message format and retry the request.
+|499 |REQUEST ERROR INVALID REQUEST ARGUMENTS |The request message was parseable, but the arguments supplied in the message were in conflict or incomplete. Check the message format and retry the request.
|500 |SERVER ERROR |A general server error occurred that prevented the request from being processed.
-|597 |SCRIPT EVALUATION ERROR |The script submitted for processing evaluated in the `ScriptEngine` with errors and could not be processed. Check the script submitted for syntax errors or other problems and then resubmit.
-|598 |SERVER TIMEOUT |The server exceeded one of the timeout settings for the request and could therefore only partially responded or did not respond at all.
-|599 |SERVER SERIALIZATION ERROR |The server was not capable of serializing an object that was returned from the script supplied on the request. Either transform the object into something Gremlin Server can process within the script or install mapper serialization classes to Gremlin Server.
+|596 |SERVER ERROR TEMPORARY |A server error occurred, but it was temporary in nature and therefore the client is free to retry it's request as-is with the potential for success.
+|597 |SERVER ERROR EVALUATION |The script submitted for processing evaluated in the `ScriptEngine` with errors and could not be processed. Check the script submitted for syntax errors or other problems and then resubmit.
+|598 |SERVER ERROR TIMEOUT |The server exceeded one of the timeout settings for the request and could therefore only partially responded or did not respond at all.
+|599 |SERVER ERROR SERIALIZATION |The server was not capable of serializing an object that was returned from the script supplied on the request. Either transform the object into something Gremlin Server can process within the script or install mapper serialization classes to Gremlin Server.
|=========================================================
NOTE: Please refer to the link:https://tinkerpop.apache.org/docs/current/dev/io[IO Reference Documentation] for more
diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc
index 411cd2f..bac1ba3 100644
--- a/docs/src/reference/gremlin-applications.asciidoc
+++ b/docs/src/reference/gremlin-applications.asciidoc
@@ -2473,6 +2473,14 @@ $ curl -X POST -d "{\"gremlin\":\"divideIt(8, 2)\"}" "http://localhost:8182"
In the above HTTP-based requests, the bindings contain a special parameter that tells the `ScriptEngine` cache to
immediately forget the script after execution. In this way, the function does not end up being globally available.
+[[request-retry]]
+==== Request Retry
+
+The server has the ability to instruct the client that an error condition is transient and that the client should
+simply retry the request later. In the event a client detects a `ResponseStatusCode` of `SERVER_ERROR_TEMPORARY`,
+which is error code `596`, the client may choose to retry that request. Note that drivers do not have the ability to
+automatically retry and that it is up to the application to provide such logic.
+
[[gremlin-server-docker-image]]
=== Docker Image
diff --git a/docs/src/reference/the-graph.asciidoc b/docs/src/reference/the-graph.asciidoc
index 91b4d95..45cd714 100644
--- a/docs/src/reference/the-graph.asciidoc
+++ b/docs/src/reference/the-graph.asciidoc
@@ -245,6 +245,9 @@ a transactional leak does occur between requests somehow, a fresh transaction is
TIP: The `tx()` method is on the `Graph` interface, but it is also available on the `TraversalSource` spawned from a
`Graph`. Calls to `TraversalSource.tx()` are proxied through to the underlying `Graph` as a convenience.
+TIP: Some graphs may throw an exception that implements `TemporaryException`. In this case, this marker interface is
+designed to inform the client that it may choose to retry the operation at a later time for possible success.
+
WARNING: TinkerPop provides for basic transaction control, however, like many aspects of TinkerPop, it is up to the
graph system provider to choose the specific aspects of how their implementation will work and how it fits into the
TinkerPop stack. Be sure to understand the transaction semantics of the specific graph implementation that is being
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 1d30532..79793ab 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -406,6 +406,24 @@ graph providers to provide this custom functionality.
See: link:https://issues.apache.org/jira/browse/TINKERPOP-2389[TINKERPOP-2389],
link:https://tinkerpop.apache.org/docs/3.5.0/reference/#authorization[Reference Documentation]
+==== Retry Conditions
+
+Some error conditions are temporary in nature and therefore an operation that ends in such a situation may be tried
+again as-is to potential success. In embedded use cases, an exception that implements the `TemporaryException`
+interface implies that the failing operation can be retried. For remote use cases, a `ResponseStatusCode` of `596`
+which equates to `SERVER_ERROR_TEMPORARY` is an indicator that a request may be retried.
+
+With this more concrete and generalized approach to determining when retries should happen, the need to trap provider
+specific exceptions or to examine the text of error messages are removed. Before replacing existing code that might
+do these things currently, it may be best to include this sort of retry checking in addition to current methods as
+it may take time for providers to support these new options. Alternatively, if you can confirm that a provider does
+support this functionality then feel free to proceed wholly with this generalized TinkerPop approach.
+
+Finally, it is important to note that TinkerPop drivers do not automatically retry when these conditions are met. It
+is up to the application to determine if retry is desired and how best to do so.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2517[TINKERPOP-2517]
+
==== Python 2.x Support
The gremlinpython module no longer supports Python 2.x. Users must use Python 3 going forward. For the most part, from
@@ -907,6 +925,18 @@ manner that they provide.
See: link:https://issues.apache.org/jira/browse/TINKERPOP-2336[TINKERPOP-2336]
+===== TemporaryException and SERVER_ERROR_TEMPORARY
+
+The `gremlin-core` module now has a `TemporaryException` interface. This interface allows providers to throw an
+exception that will be considered by users to be generally retryable. In addition, the Gremlin Server protocol now
+also has a `ResponseStatusCode.SERVER_ERROR_TEMPORARY` status which indicates the same situation. Throwing an exception
+that implements `TemporaryException` will be recognized by Gremlin Server to return this error code. This notion of
+"temporary failure" is helpful to providers as it allows them to let users know that a failure is transient and related
+to the system state at the time of the request. Without this indicator, users are left to parse exception messages to
+determine when it is considered acceptable to retry an operation.
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2517[TINKERPOP-2517]
+
==== Graph Driver Providers
===== TraversalOpProcessor Side-effects
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/TemporaryException.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/TemporaryException.java
new file mode 100644
index 0000000..99f06f2
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/util/TemporaryException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.structure.util;
+
+/**
+ * Any exception that implements this interface will be recognized as one whose throwing operation is correct but can
+ * be retried. In other words, the state of the system may be such that a failure, like a database locking error,
+ * could be resolved for the same request at a future time.
+ */
+public interface TemporaryException {
+}
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
index 9daee96..d6b301f 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
@@ -33,39 +33,52 @@ import java.util.stream.Stream;
public enum ResponseStatusCode {
/**
* The server successfully processed a request to completion - there are no messages remaining in this stream.
+ *
+ * @since 3.0.0-incubating
*/
SUCCESS(200),
/**
* The server processed the request but there is no result to return (e.g. an {@link Iterator} with no elements).
+ *
+ * @since 3.0.0-incubating
*/
NO_CONTENT(204),
/**
* The server successfully returned some content, but there is more in the stream to arrive - wait for a
* {@link #SUCCESS} to signify the end of the stream.
+ *
+ * @since 3.0.0-incubating
*/
PARTIAL_CONTENT(206),
/**
* The server could not authenticate the request or the client requested a resource it did not have access to.
+ *
+ * @since 3.0.0-incubating
*/
UNAUTHORIZED(401),
/**
* The server could authenticate the request, but will not fulfill it. This is a general purpose code that
- * would typically be returned if the request is authenticated but not authorized to do what it is doing. This
- * code is for future use.
+ * would typically be returned if the request is authenticated but not authorized to do what it is doing.
+ *
+ * @since 3.0.1-incubating
*/
FORBIDDEN(403),
/**
* A challenge from the server for the client to authenticate its request.
+ *
+ * @since 3.0.1-incubating
*/
AUTHENTICATE(407),
/**
* The request message contains objects that were not serializable on the client side.
+ *
+ * @since 3.3.6
*/
REQUEST_ERROR_SERIALIZATION(497),
@@ -73,29 +86,49 @@ public enum ResponseStatusCode {
* The request message was not properly formatted which means it could not be parsed at all or the "op" code was
* not recognized such that Gremlin Server could properly route it for processing. Check the message format and
* retry the request.
+ *
+ * @since 3.0.0-incubating
*/
REQUEST_ERROR_MALFORMED_REQUEST(498),
/**
* The request message was parseable, but the arguments supplied in the message were in conflict or incomplete.
* Check the message format and retry the request.
+ *
+ * @since 3.0.0-incubating
*/
REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS(499),
/**
* A general server error occurred that prevented the request from being processed.
+ *
+ * @since 3.0.0-incubating
*/
SERVER_ERROR(500),
/**
+ * A server error that indicates that the client should retry the request. A graph will typically return this error
+ * when a transaction fails due to a locking exception or some other sort of concurrent modification. In other
+ * words, the request was likely valid but the state of the server at the particular time the request arrived
+ * could not be processed to success, but could be at a later moment.
+ *
+ * @since 3.4.11
+ */
+ SERVER_ERROR_TEMPORARY(596),
+
+ /**
* The request submitted for processing evaluated by the server with errors and could not be processed.
* Check the script or remote traversal submitted for errors or other problems and then resubmit.
+ *
+ * @since 3.0.0-incubating
*/
SERVER_ERROR_EVALUATION(597),
/**
* The server exceeded one of the timeout settings for the request and could therefore only partially responded
* or did not respond at all.
+ *
+ * @since 3.0.0-incubating
*/
SERVER_ERROR_TIMEOUT(598),
@@ -103,11 +136,13 @@ public enum ResponseStatusCode {
* The server was not capable of serializing an object that was returned from the script supplied on the request.
* Either transform the object into something Gremlin Server can process within the script or install mapper
* serialization classes to Gremlin Server.
+ *
+ * @since 3.0.0-incubating
*/
SERVER_ERROR_SERIALIZATION(599);
private final int value;
- private static Map<Integer, ResponseStatusCode> codeValueMap = new HashMap<>();
+ private final static Map<Integer, ResponseStatusCode> codeValueMap = new HashMap<>();
static {
Stream.of(ResponseStatusCode.values()).forEach(code -> codeValueMap.put(code.getValue(), code));
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
index e04cd71..49cc947 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
@@ -275,7 +275,14 @@ public abstract class AbstractEvalOpProcessor extends AbstractOpProcessor {
timerContext.stop();
if (t != null) {
- if (t instanceof OpProcessorException) {
+ // if any exception in the chain is TemporaryException then we should respond with the right error
+ // code so that the client knows to retry
+ final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(t);
+ if (possibleTemporaryException.isPresent()) {
+ ctx.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
+ .statusMessage(possibleTemporaryException.get().getMessage())
+ .statusAttributeException(possibleTemporaryException.get()).create());
+ } else if (t instanceof OpProcessorException) {
ctx.writeAndFlush(((OpProcessorException) t).getResponseMessage());
} else if (t instanceof TimedInterruptTimeoutException) {
// occurs when the TimedInterruptCustomizerProvider is in play
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
index 735f251..a616ffb 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
@@ -19,6 +19,7 @@
package org.apache.tinkerpop.gremlin.server.op;
import io.netty.channel.ChannelHandlerContext;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.tinkerpop.gremlin.driver.MessageSerializer;
import org.apache.tinkerpop.gremlin.driver.Tokens;
import org.apache.tinkerpop.gremlin.driver.message.RequestMessage;
@@ -32,6 +33,7 @@ import org.apache.tinkerpop.gremlin.server.Settings;
import org.apache.tinkerpop.gremlin.server.handler.Frame;
import org.apache.tinkerpop.gremlin.server.handler.StateKey;
import org.apache.tinkerpop.gremlin.server.util.ExceptionHelper;
+import org.apache.tinkerpop.gremlin.structure.util.TemporaryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,7 +44,9 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
/**
* A base {@link OpProcessor} implementation that processes an {@code Iterator} of results in a generalized way while
@@ -63,6 +67,15 @@ public abstract class AbstractOpProcessor implements OpProcessor {
}
/**
+ * Check if any exception in the chain is TemporaryException then we should respond with the right error code so
+ * that the client knows to retry.
+ */
+ protected static Optional<Throwable> determineIfTemporaryException(final Throwable ex) {
+ return Stream.of(ExceptionUtils.getThrowables(ex)).
+ filter(i -> i instanceof TemporaryException).findFirst();
+ }
+
+ /**
* Provides a generic way of iterating a result set back to the client.
*
* @param context The Gremlin Server {@link Context} object containing settings, request message, etc.
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
index 1d969a8..0697b5b 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
@@ -224,26 +224,39 @@ public class TraversalOpProcessor extends AbstractOpProcessor {
if (ex instanceof UndeclaredThrowableException)
t = t.getCause();
- if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) {
+ // if any exception in the chain is TemporaryException then we should respond with the right error
+ // code so that the client knows to retry
+ final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
+ if (possibleTemporaryException.isPresent()) {
+ context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
+ .statusMessage(possibleTemporaryException.get().getMessage())
+ .statusAttributeException(possibleTemporaryException.get()).create());
+ } else if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) {
final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", msg);
logger.warn(errorMessage);
context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
.statusMessage(errorMessage)
.statusAttributeException(ex).create());
- onError(graph, context);
} else {
logger.warn(String.format("Exception processing a Traversal on iteration for request [%s].", msg.getRequestId()), ex);
context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
.statusMessage(ex.getMessage())
.statusAttributeException(ex).create());
- onError(graph, context);
}
+ onError(graph, context);
}
} catch (Exception ex) {
- logger.warn(String.format("Exception processing a Traversal on request [%s].", msg.getRequestId()), ex);
- context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
- .statusMessage(ex.getMessage())
- .statusAttributeException(ex).create());
+ final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
+ if (possibleTemporaryException.isPresent()) {
+ context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
+ .statusMessage(possibleTemporaryException.get().getMessage())
+ .statusAttributeException(possibleTemporaryException.get()).create());
+ } else {
+ logger.warn(String.format("Exception processing a Traversal on request [%s].", msg.getRequestId()), ex);
+ context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
+ .statusMessage(ex.getMessage())
+ .statusAttributeException(ex).create());
+ }
onError(graph, context);
} finally {
timerContext.stop();
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/DefaultTemporaryException.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/DefaultTemporaryException.java
new file mode 100644
index 0000000..e31a240
--- /dev/null
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/DefaultTemporaryException.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.server.util;
+
+import org.apache.tinkerpop.gremlin.structure.util.TemporaryException;
+
+/**
+ * This is a default {@link TemporaryException} implementation that server implementers could use if they wished
+ * to indicate retry operations to the client. It is an overly simple class meant more for testing than be extended and
+ * providers are encouraged to tag their own exceptions more directly with the {@link TemporaryException} interface
+ * or to develop their own.
+ */
+public final class DefaultTemporaryException extends Exception implements TemporaryException {
+ public DefaultTemporaryException() {
+ }
+
+ public DefaultTemporaryException(final String message) {
+ super(message);
+ }
+
+ public DefaultTemporaryException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public DefaultTemporaryException(final Throwable cause) {
+ super(cause);
+ }
+
+ public DefaultTemporaryException(final String message, final Throwable cause, final boolean enableSuppression,
+ final boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
index 6e9f166..5bd128a 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
@@ -973,4 +973,20 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
assertThat(ex.getMessage(), containsString("The Gremlin statement that was submitted exceeds the maximum compilation size allowed by the JVM"));
}
}
+
+ @Test
+ public void shouldGenerateTemporaryErrorResponseStatusCode() throws Exception {
+ final Cluster cluster = TestClientFactory.build().create();
+ final Client client = cluster.connect();
+
+ try {
+ client.submit("g.addV('person').sideEffect{throw new org.apache.tinkerpop.gremlin.server.util.DefaultTemporaryException('try again!')}").all().get();
+ fail("Should have tanked since we threw an exception out manually");
+ } catch (Exception ex) {
+ final Throwable t = ex.getCause();
+ assertThat(t, instanceOf(ResponseException.class));
+ assertEquals("try again!", t.getMessage());
+ assertEquals(ResponseStatusCode.SERVER_ERROR_TEMPORARY, ((ResponseException) t).getResponseStatusCode());
+ }
+ }
}