Skip to content

Commit 370d60b

Browse files
authored
Merge pull request neo4j#156 from zhenlineo/1.2-not-retry-on-user-canceled-tx
Support transient but not terminated by user errors in retry
2 parents 8b77616 + f960b41 commit 370d60b

File tree

3 files changed

+57
-5
lines changed

3 files changed

+57
-5
lines changed

neo4j/v1/api.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,12 @@
2727

2828
from neo4j.bolt import ProtocolError, ServiceUnavailable
2929
from neo4j.compat import urlparse
30-
from neo4j.exceptions import CypherError
30+
from neo4j.exceptions import CypherError, TransientError
3131

3232
from .exceptions import DriverError, SessionError, SessionExpired, TransactionError
3333

34-
3534
_warned_about_transaction_bookmarks = False
3635

37-
3836
READ_ACCESS = "READ"
3937
WRITE_ACCESS = "WRITE"
4038

@@ -53,7 +51,6 @@ def retry_delay_generator(initial_delay, multiplier, jitter_factor):
5351

5452

5553
class ValueSystem(object):
56-
5754
def hydrate(self, values):
5855
""" Hydrate values from raw representations into client objects.
5956
"""
@@ -435,6 +432,11 @@ def _run_transaction(self, access_mode, unit_of_work, *args, **kwargs):
435432
return unit_of_work(tx, *args, **kwargs)
436433
except (ServiceUnavailable, SessionExpired) as error:
437434
last_error = error
435+
except TransientError as error:
436+
if is_retriable_transientError(error):
437+
last_error = error
438+
else:
439+
raise error
438440
sleep(next(retry_delay))
439441
t1 = clock()
440442
raise last_error
@@ -461,6 +463,14 @@ def __bookmark__(self, result):
461463
pass
462464

463465

466+
def is_retriable_transientError(error):
467+
"""
468+
:type error: TransientError
469+
"""
470+
return not (error.code in ("Neo.TransientError.Transaction.Terminated",
471+
"Neo.TransientError.Transaction.LockClientStopped"))
472+
473+
464474
class Transaction(object):
465475
""" Container for multiple Cypher queries to be executed within
466476
a single context. Transactions can be used within a :py:const:`with`
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
!: AUTO INIT
2+
!: AUTO RESET
3+
4+
C: RUN "BEGIN" {}
5+
PULL_ALL
6+
S: SUCCESS {"fields": []}
7+
SUCCESS {}
8+
9+
C: RUN "RETURN 1" {}
10+
PULL_ALL
11+
S: FAILURE {"code": "Neo.TransientError.Transaction.LockClientStopped", "message": "X"}
12+
IGNORED {}
13+
14+
C: ACK_FAILURE
15+
S: SUCCESS {}
16+
17+
C: RUN "ROLLBACK" {}
18+
PULL_ALL
19+
S: SUCCESS {}
20+
SUCCESS {}

test/stub/test_accesslevel.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
# limitations under the License.
2020

2121

22-
from neo4j.v1 import GraphDatabase, CypherError
22+
from neo4j.v1 import GraphDatabase, CypherError, TransientError
2323

2424
from test.stub.tools import StubTestCase, StubCluster
2525

@@ -159,3 +159,25 @@ def unit_of_work_2(tx):
159159
assert value == 1
160160
value = session.read_transaction(unit_of_work_2)
161161
assert value == 2
162+
163+
def test_no_retry_read_on_user_canceled_tx(self):
164+
with StubCluster({9001: "router.script", 9004: "user_canceled_tx.script.script"}):
165+
uri = "bolt+routing://127.0.0.1:9001"
166+
with GraphDatabase.driver(uri, auth=self.auth_token, encrypted=False) as driver:
167+
with driver.session() as session:
168+
def unit_of_work(tx):
169+
tx.run("RETURN 1")
170+
171+
with self.assertRaises(TransientError):
172+
_ = session.read_transaction(unit_of_work)
173+
174+
def test_no_retry_write_on_user_canceled_tx(self):
175+
with StubCluster({9001: "router.script", 9006: "user_canceled_tx.script.script"}):
176+
uri = "bolt+routing://127.0.0.1:9001"
177+
with GraphDatabase.driver(uri, auth=self.auth_token, encrypted=False) as driver:
178+
with driver.session() as session:
179+
def unit_of_work(tx):
180+
tx.run("RETURN 1")
181+
182+
with self.assertRaises(TransientError):
183+
_ = session.write_transaction(unit_of_work)

0 commit comments

Comments
 (0)