Skip to content

Commit f2312da

Browse files
bigmontzfbiville
andauthored
Reclassify Transaction.LockClientStopped and Transaction.Terminated as ClientError (#945)
* 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 <[email protected]>
1 parent f67e4f4 commit f2312da

File tree

6 files changed

+107
-26
lines changed

6 files changed

+107
-26
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
@@ -129,30 +129,16 @@ function _isRetriableCode (code?: Neo4jErrorCode): boolean {
129129
return code === SERVICE_UNAVAILABLE ||
130130
code === SESSION_EXPIRED ||
131131
_isAuthorizationExpired(code) ||
132-
_isRetriableTransientError(code)
132+
_isTransientError(code)
133133
}
134134

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

158144
/**

packages/core/test/error.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,17 @@ function getRetriableCodes (): string[] {
165165
SESSION_EXPIRED,
166166
'Neo.ClientError.Security.AuthorizationExpired',
167167
'Neo.TransientError.Transaction.DeadlockDetected',
168-
'Neo.TransientError.Network.CommunicationError'
168+
'Neo.TransientError.Network.CommunicationError',
169+
'Neo.TransientError.Transaction.Terminated',
170+
'Neo.TransientError.Transaction.LockClientStopped'
169171
]
170172
}
171173

172174
function getNonRetriableCodes (): string[] {
173175
return [
174-
'Neo.TransientError.Transaction.Terminated',
175176
'Neo.DatabaseError.General.UnknownError',
176-
'Neo.TransientError.Transaction.LockClientStopped',
177-
'Neo.DatabaseError.General.OutOfMemoryError'
177+
'Neo.DatabaseError.General.OutOfMemoryError',
178+
'Neo.ClientError.Transaction.Terminated',
179+
'Neo.ClientError.Transaction.LockClientStopped'
178180
]
179181
}

packages/neo4j-driver/test/internal/retry-logic-rx.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('#unit-rx retrylogic', () => {
6565
verifyNoRetry(
6666
newError(
6767
'transaction terminated',
68-
'Neo.TransientError.Transaction.Terminated'
68+
'Neo.ClientError.Transaction.Terminated'
6969
)
7070
)
7171
})
@@ -74,7 +74,7 @@ describe('#unit-rx retrylogic', () => {
7474
verifyNoRetry(
7575
newError(
7676
'lock client stopped',
77-
'Neo.TransientError.Transaction.LockClientStopped'
77+
'Neo.ClientError.Transaction.LockClientStopped'
7878
)
7979
)
8080
})

packages/neo4j-driver/test/internal/transaction-executor.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ const { SERVICE_UNAVAILABLE, SESSION_EXPIRED } = err
3030
const TRANSIENT_ERROR_1 = 'Neo.TransientError.Transaction.DeadlockDetected'
3131
const TRANSIENT_ERROR_2 = 'Neo.TransientError.Network.CommunicationError'
3232
const UNKNOWN_ERROR = 'Neo.DatabaseError.General.UnknownError'
33-
const TX_TERMINATED_ERROR = 'Neo.TransientError.Transaction.Terminated'
33+
const TX_TERMINATED_ERROR = 'Neo.ClientError.Transaction.Terminated'
3434
const LOCKS_TERMINATED_ERROR =
35-
'Neo.TransientError.Transaction.LockClientStopped'
35+
'Neo.ClientError.Transaction.LockClientStopped'
3636
const OOM_ERROR = 'Neo.DatabaseError.General.OutOfMemoryError'
3737

3838
describe('#unit TransactionExecutor', () => {

0 commit comments

Comments
 (0)