Skip to content

Commit 3d78c89

Browse files
bigmontzfbiville
andcommitted
Reclassify Transaction.LockClientStopped and Transaction.Terminated as ClientError
The server 5.x already fix this classification error, but the driver has re-classify errors from previous version of the server for a more convenience for the user. Co-authored-by: Florent Biville <florent.biville@neo4j.com>
1 parent cc4598b commit 3d78c89

File tree

4 files changed

+103
-22
lines changed

4 files changed

+103
-22
lines changed

packages/bolt-connection/src/bolt/response-handler.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ export default class ResponseHandler {
117117
this._log.debug(`S: FAILURE ${json.stringify(msg)}`)
118118
}
119119
try {
120-
const error = newError(payload.message, payload.code)
120+
const standardizedCode = _standardizeCode(payload.code)
121+
const error = newError(payload.message, standardizedCode)
121122
this._currentFailure = this._observer.onErrorApplyTransformation(
122123
error
123124
)
@@ -194,3 +195,21 @@ export default class ResponseHandler {
194195
this._currentFailure = null
195196
}
196197
}
198+
199+
/**
200+
* Standardize error classification that are different between 5.x and previous versions.
201+
*
202+
* The transient error were clean-up for being retrieable and because of this
203+
* `Terminated` and `LockClientStopped` were reclassified as `ClientError`.
204+
*
205+
* @param {string} code
206+
* @returns {string} the standardized error code
207+
*/
208+
function _standardizeCode (code) {
209+
if (code === 'Neo.TransientError.Transaction.Terminated') {
210+
return 'Neo.ClientError.Transaction.Terminated'
211+
} else if (code === 'Neo.TransientError.Transaction.LockClientStopped') {
212+
return 'Neo.ClientError.Transaction.LockClientStopped'
213+
}
214+
return code
215+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import ResponseHandler from '../../src/bolt/response-handler'
21+
import { internal, newError } from 'neo4j-driver-core'
22+
23+
const {
24+
logger: { Logger }
25+
} = internal
26+
27+
const FAILURE = 0x7f // 0111 1111 // FAILURE <metadata>
28+
29+
describe('response-handler', () => {
30+
describe('.handleResponse()', () => {
31+
it.each([
32+
{
33+
code: 'Neo.TransientError.Transaction.Terminated',
34+
expectedErrorCode: 'Neo.ClientError.Transaction.Terminated'
35+
},
36+
{
37+
code: 'Neo.TransientError.Transaction.LockClientStopped',
38+
expectedErrorCode: 'Neo.ClientError.Transaction.LockClientStopped'
39+
},
40+
{
41+
code: 'Neo.ClientError.Transaction.LockClientStopped',
42+
expectedErrorCode: 'Neo.ClientError.Transaction.LockClientStopped'
43+
},
44+
{
45+
code: 'Neo.ClientError.Transaction.Terminated',
46+
expectedErrorCode: 'Neo.ClientError.Transaction.Terminated'
47+
},
48+
{
49+
code: 'Neo.TransientError.Security.NotYourBusiness',
50+
expectedErrorCode: 'Neo.TransientError.Security.NotYourBusiness'
51+
}
52+
])('should fix wrong classified error codes', ({ code, expectedErrorCode }) => {
53+
const observer = {
54+
capturedErrors: [],
55+
onFailure: error => observer.capturedErrors.push(error)
56+
}
57+
const message = 'Something gets wrong'
58+
const expectedError = newError(message, expectedErrorCode)
59+
const responseHandler = new ResponseHandler({ observer, log: Logger.noOp() })
60+
responseHandler._queueObserver({})
61+
62+
const errorMessage = {
63+
signature: FAILURE,
64+
fields: [{ message, code }]
65+
}
66+
responseHandler.handleResponse(errorMessage)
67+
68+
expect(observer.capturedErrors.length).toBe(1)
69+
const [receivedError] = observer.capturedErrors
70+
expect(receivedError.message).toBe(expectedError.message)
71+
expect(receivedError.code).toBe(expectedError.code)
72+
})
73+
})
74+
})

packages/core/src/error.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,30 +127,16 @@ function _isRetriableCode (code?: Neo4jErrorCode): boolean {
127127
return code === SERVICE_UNAVAILABLE ||
128128
code === SESSION_EXPIRED ||
129129
_isAuthorizationExpired(code) ||
130-
_isRetriableTransientError(code)
130+
_isTransientError(code)
131131
}
132132

133133
/**
134134
* @private
135135
* @param {string} code the error to check
136136
* @return {boolean} true if the error is a transient error
137137
*/
138-
function _isRetriableTransientError (code?: Neo4jErrorCode): boolean {
139-
// Retries should not happen when transaction was explicitly terminated by the user.
140-
// Termination of transaction might result in two different error codes depending on where it was
141-
// terminated. These are really client errors but classification on the server is not entirely correct and
142-
// they are classified as transient.
143-
144-
if (code?.includes('TransientError') === true) {
145-
if (
146-
code === 'Neo.TransientError.Transaction.Terminated' ||
147-
code === 'Neo.TransientError.Transaction.LockClientStopped'
148-
) {
149-
return false
150-
}
151-
return true
152-
}
153-
return false
138+
function _isTransientError (code?: Neo4jErrorCode): boolean {
139+
return code?.includes('TransientError') === true
154140
}
155141

156142
/**

packages/core/test/error.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,17 @@ function getRetriableCodes (): string[] {
132132
SESSION_EXPIRED,
133133
'Neo.ClientError.Security.AuthorizationExpired',
134134
'Neo.TransientError.Transaction.DeadlockDetected',
135-
'Neo.TransientError.Network.CommunicationError'
135+
'Neo.TransientError.Network.CommunicationError',
136+
'Neo.TransientError.Transaction.Terminated',
137+
'Neo.TransientError.Transaction.LockClientStopped'
136138
]
137139
}
138140

139141
function getNonRetriableCodes (): string[] {
140142
return [
141-
'Neo.TransientError.Transaction.Terminated',
142143
'Neo.DatabaseError.General.UnknownError',
143-
'Neo.TransientError.Transaction.LockClientStopped',
144-
'Neo.DatabaseError.General.OutOfMemoryError'
144+
'Neo.DatabaseError.General.OutOfMemoryError',
145+
'Neo.ClientError.Transaction.Terminated',
146+
'Neo.ClientError.Transaction.LockClientStopped'
145147
]
146148
}

0 commit comments

Comments
 (0)