This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/master by this push:
new 6b53bb6a49 Report malformed gremlin-go responses as deserialization
errors
6b53bb6a49 is described below
commit 6b53bb6a49053c2d5e8fc38c669775b3201eab3f
Author: Stephen Mallette <[email protected]>
AuthorDate: Wed Jul 29 09:36:24 2026 -0400
Report malformed gremlin-go responses as deserialization errors
A malformed or truncated GraphBinary response surfaced whichever bare
message the decoder happened to produce, including a typo'd "marker value
erorr". Decode failures are now wrapped as deserialization errors.
Assisted-by: Claude Code:claude-opus-5
---
CHANGELOG.asciidoc | 1 +
gremlin-go/driver/connection.go | 24 +++++++++++++---------
gremlin-go/driver/error_codes.go | 1 +
gremlin-go/driver/marker.go | 4 +---
gremlin-go/driver/resources/error-messages/en.json | 1 +
5 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index e588872e70..50a76c696c 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -52,6 +52,7 @@
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Fixed `gremlin-dotnet` deflate response decompression, which threw on the
server's zlib-framed output because it used `DeflateStream` (raw DEFLATE, RFC
1951) instead of `ZLibStream` (zlib, RFC 1950); the bug was previously masked
because compression was off by default.
* Fixed `gremlin-dotnet` SSL options cloning (used on the
skip-certificate-validation path) to copy `ClientCertificateContext` and
`AllowTlsResume`, which were previously dropped, breaking mTLS client
certificates and silently re-enabling TLS resumption.
* Fixed `gremlin-python` read timeout to derive from a single source (aiohttp
`sock_read`), removing a redundant `async_timeout` read wrapper that could race
it; a read timeout now deterministically raises `ReadTimeoutError` (a builtin
`TimeoutError` subclass).
+* Fixed `gremlin-go` to report a malformed or truncated GraphBinary response
as a deserialization error rather than a bare decoder message.
* Removed `Transaction.open()` in favor of `begin()`, which is now the single
transaction-start primitive across embedded and remote contexts.
* Changed `begin()` and `close()` to be idempotent and calling it when a
transaction is already in that state no longer throws.
* Added `maxTransactionLifetimeMillis` setting to Gremlin Server, an absolute
cap on the total age of an HTTP transaction that interrupts a running operation
and rolls the transaction back when it fires (default 600000ms, set to `0` to
disable).
diff --git a/gremlin-go/driver/connection.go b/gremlin-go/driver/connection.go
index 93502910c3..8686fe8f84 100644
--- a/gremlin-go/driver/connection.go
+++ b/gremlin-go/driver/connection.go
@@ -387,6 +387,15 @@ func (c *connection) getReader(resp *http.Response)
(io.Reader, io.Closer, error
return resp.Body, nil, nil
}
+// setDeserializationError reports a failure to decode the GraphBinary
response stream. The
+// underlying error is wrapped so that callers can tell a malformed or
truncated response apart
+// from a transport failure, regardless of which point in the stream the
decode gave out at.
+func (c *connection) setDeserializationError(rs ResultSet, err error) {
+ deserErr := fmt.Errorf("failed to deserialize response: %w", err)
+ c.logHandler.logf(Error, failedToReceiveResponse, deserErr.Error())
+ rs.setError(deserErr)
+}
+
func (c *connection) streamToResultSet(reader io.Reader, rs ResultSet) {
var d *GraphBinaryDeserializer
if c.connSettings.pdtRegistry != nil {
@@ -400,8 +409,7 @@ func (c *connection) streamToResultSet(reader io.Reader, rs
ResultSet) {
c.logHandler.logf(Error, failedToReceiveResponse,
emptyBodyErr.Error())
rs.setError(emptyBodyErr)
} else {
- c.logHandler.logf(Error, failedToReceiveResponse,
err.Error())
- rs.setError(err)
+ c.setDeserializationError(rs, err)
}
return
}
@@ -410,8 +418,7 @@ func (c *connection) streamToResultSet(reader io.Reader, rs
ResultSet) {
obj, err := d.ReadFullyQualified()
if err != nil {
if err != io.EOF {
- c.logHandler.logf(Error,
failedToReceiveResponse, err.Error())
- rs.setError(err)
+ c.setDeserializationError(rs, err)
}
return
}
@@ -419,8 +426,7 @@ func (c *connection) streamToResultSet(reader io.Reader, rs
ResultSet) {
if marker, ok := obj.(Marker); ok && marker == EndOfStream() {
code, msg, _, err := d.ReadStatus()
if err != nil {
- c.logHandler.logf(Error,
failedToReceiveResponse, err.Error())
- rs.setError(err)
+ c.setDeserializationError(rs, err)
return
}
if code != 200 && code != 0 {
@@ -432,14 +438,12 @@ func (c *connection) streamToResultSet(reader io.Reader,
rs ResultSet) {
if d.IsBulked() {
bulkObj, err := d.ReadFullyQualified()
if err != nil {
- c.logHandler.logf(Error,
failedToReceiveResponse, err.Error())
- rs.setError(err)
+ c.setDeserializationError(rs, err)
return
}
bulk, ok := bulkObj.(int64)
if !ok {
- c.logHandler.logf(Error,
failedToReceiveResponse, "expected int64 bulk count")
- rs.setError(fmt.Errorf("expected int64 bulk
count, got %T", bulkObj))
+ c.setDeserializationError(rs,
fmt.Errorf("expected int64 bulk count, got %T", bulkObj))
return
}
rs.Channel() <- &Result{&Traverser{Bulk: bulk, Value:
obj}}
diff --git a/gremlin-go/driver/error_codes.go b/gremlin-go/driver/error_codes.go
index 9e727c0460..2927ab05c5 100644
--- a/gremlin-go/driver/error_codes.go
+++ b/gremlin-go/driver/error_codes.go
@@ -54,6 +54,7 @@ const (
err0406EnumReaderInvalidTypeError errorCode =
"E0406_GRAPH_BINARY_ENUMREADER_INVALID_TYPE_ERROR"
err0407GetSerializerToWriteUnknownTypeError errorCode =
"E0407_GRAPH_BINARY_GETSERIALIZERTOWRITE_UNKNOWN_TYPE_ERROR"
err0408GetSerializerToReadUnknownTypeError errorCode =
"E0408_GRAPH_BINARY_GETSERIALIZERTOREAD_UNKNOWN_TYPE_ERROR"
+ err0410UnexpectedMarkerValueError errorCode =
"E0410_GRAPH_BINARY_UNEXPECTED_MARKER_VALUE_ERROR"
// response handling errors
err0501ResponseResultSetNotCreatedError errorCode =
"E0501_RESPONSE_NO_RESULTSET_ON_DATA_RECEIVE"
diff --git a/gremlin-go/driver/marker.go b/gremlin-go/driver/marker.go
index 435508c3b8..fe056e767b 100644
--- a/gremlin-go/driver/marker.go
+++ b/gremlin-go/driver/marker.go
@@ -19,8 +19,6 @@ under the License.
package gremlingo
-import "fmt"
-
// Marker is used in response
type Marker interface {
GetValue() byte
@@ -40,7 +38,7 @@ func (m marker) GetValue() byte {
func Of(value byte) (Marker, error) {
if value != 0 {
- return nil, fmt.Errorf("marker value erorr")
+ return nil, newError(err0410UnexpectedMarkerValueError, value)
}
return EndOfStream(), nil
}
diff --git a/gremlin-go/driver/resources/error-messages/en.json
b/gremlin-go/driver/resources/error-messages/en.json
index 0c8fa6c4bd..bcc54552b8 100644
--- a/gremlin-go/driver/resources/error-messages/en.json
+++ b/gremlin-go/driver/resources/error-messages/en.json
@@ -20,6 +20,7 @@
"E0407_GRAPH_BINARY_GETSERIALIZERTOWRITE_UNKNOWN_TYPE_ERROR":"E0407: unknown
data type to serialize %s",
"E0408_GRAPH_BINARY_GETSERIALIZERTOREAD_UNKNOWN_TYPE_ERROR": "E0408: unknown
data type to deserialize 0x%x",
"E0409_GRAPH_BINARY_GETSERIALIZERTOREAD_UNKNOWN_CUSTOM_TYPE_ERROR": "E0409:
unknown custom data type to deserialize %s",
+ "E0410_GRAPH_BINARY_UNEXPECTED_MARKER_VALUE_ERROR": "E0410: failed to
deserialize marker, expected end-of-stream value 0x00 but read 0x%x",
"E0501_RESPONSE_NO_RESULTSET_ON_DATA_RECEIVE":"E0501: resultSet was not
created before data was received",
"E0502_RESPONSE_ERROR": "E0502: error handling response, error message
'%+v'. statusCode: %d",