16
16
use Exception ;
17
17
use Laudis \Neo4j \Common \GeneratorHelper ;
18
18
use Laudis \Neo4j \Common \Neo4jLogger ;
19
- use Laudis \Neo4j \Common \TransactionHelper ;
20
19
use Laudis \Neo4j \Contracts \ConnectionPoolInterface ;
20
+ use Laudis \Neo4j \Contracts \CypherSequence ;
21
21
use Laudis \Neo4j \Contracts \SessionInterface ;
22
22
use Laudis \Neo4j \Contracts \TransactionInterface ;
23
23
use Laudis \Neo4j \Contracts \UnmanagedTransactionInterface ;
@@ -41,6 +41,7 @@ final class Session implements SessionInterface
41
41
{
42
42
/** @psalm-readonly */
43
43
private readonly BookmarkHolder $ bookmarkHolder ;
44
+ private const ROLLBACK_CLASSIFICATIONS = ['ClientError ' , 'TransientError ' , 'DatabaseError ' ];
44
45
45
46
/**
46
47
* @param ConnectionPool|Neo4jConnectionPool $pool
@@ -100,21 +101,59 @@ public function writeTransaction(callable $tsxHandler, ?TransactionConfiguration
100
101
$ this ->getLogger ()?->log(LogLevel::INFO , 'Beginning write transaction ' , ['config ' => $ config ]);
101
102
$ config = $ this ->mergeTsxConfig ($ config );
102
103
103
- return TransactionHelper::retry (
104
- fn () => $ this ->startTransaction ($ config , $ this ->config ->withAccessMode (AccessMode::WRITE ())),
105
- $ tsxHandler
106
- );
104
+ return $ this ->retry ($ tsxHandler , false , $ config );
107
105
}
108
106
109
107
public function readTransaction (callable $ tsxHandler , ?TransactionConfiguration $ config = null )
110
108
{
111
109
$ this ->getLogger ()?->log(LogLevel::INFO , 'Beginning read transaction ' , ['config ' => $ config ]);
112
110
$ config = $ this ->mergeTsxConfig ($ config );
113
111
114
- return TransactionHelper::retry (
115
- fn () => $ this ->startTransaction ($ config , $ this ->config ->withAccessMode (AccessMode::READ ())),
116
- $ tsxHandler
117
- );
112
+ return $ this ->retry ($ tsxHandler , true , $ config );
113
+ }
114
+
115
+ /**
116
+ * @template U
117
+ *
118
+ * @param callable(TransactionInterface):U $tsxHandler
119
+ *
120
+ * @return U
121
+ */
122
+ private function retry (callable $ tsxHandler , bool $ read , TransactionConfiguration $ config )
123
+ {
124
+ while (true ) {
125
+ $ transaction = null ;
126
+ try {
127
+ if ($ read ) {
128
+ $ transaction = $ this ->startTransaction ($ config , $ this ->config ->withAccessMode (AccessMode::READ ()));
129
+ } else {
130
+ $ transaction = $ this ->startTransaction ($ config , $ this ->config ->withAccessMode (AccessMode::WRITE ()));
131
+ }
132
+ $ tbr = $ tsxHandler ($ transaction );
133
+ self ::triggerLazyResult ($ tbr );
134
+ $ transaction ->commit ();
135
+
136
+ return $ tbr ;
137
+ } catch (Neo4jException $ e ) {
138
+ if ($ transaction && !in_array ($ e ->getClassification (), self ::ROLLBACK_CLASSIFICATIONS )) {
139
+ $ transaction ->rollback ();
140
+ }
141
+
142
+ if ($ e ->getTitle () === 'NotALeader ' ) {
143
+ // By closing the pool, we force the connection to be re-acquired and the routing table to be refetched
144
+ $ this ->pool ->close ();
145
+ } elseif ($ e ->getClassification () !== 'TransientError ' ) {
146
+ throw $ e ;
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ private static function triggerLazyResult (mixed $ tbr ): void
153
+ {
154
+ if ($ tbr instanceof CypherSequence) {
155
+ $ tbr ->preload ();
156
+ }
118
157
}
119
158
120
159
public function transaction (callable $ tsxHandler , ?TransactionConfiguration $ config = null )
0 commit comments