Skip to content

Commit 5b31ac7

Browse files
committed
Document necessary try-catch blocks and remove redundant ones
1 parent c5de06c commit 5b31ac7

3 files changed

Lines changed: 34 additions & 12 deletions

File tree

src/Bolt/BoltConnection.php

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@ public function __destruct()
315315

316316
public function close(): void
317317
{
318+
// Graceful cleanup: GOODBYE/DISCARD may fail if connection already broken.
319+
// Must catch to ensure unset($this->boltProtocol) executes,
320+
// preventing pool from reusing broken connections.
318321
try {
319322
if ($this->isOpen()) {
320323
if ($this->isStreaming()) {
@@ -346,15 +349,7 @@ public function close(): void
346349
public function invalidate(): void
347350
{
348351
$this->subscribedResults = [];
349-
try {
350-
$this->connection->disconnect();
351-
} catch (Throwable $e) {
352-
$this->logger?->log(LogLevel::WARNING, 'Failed to disconnect during invalidation', [
353-
'exception' => $e->getMessage(),
354-
'type' => $e::class,
355-
]);
356-
}
357-
352+
$this->connection->disconnect();
358353
unset($this->boltProtocol);
359354
}
360355

@@ -444,6 +439,7 @@ public function assertNoFailure(Response $response): void
444439

445440
/**
446441
* Discard unconsumed results - sends DISCARD to server for each subscribed result.
442+
* Try-catch prevents DISCARD failures from breaking cleanup chain in Session.close().
447443
*/
448444
public function discardUnconsumedResults(): void
449445
{

src/Bolt/BoltResult.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,15 @@ public function consume(): array
105105

106106
private function fetchResults(): void
107107
{
108+
// Catch socket/connection errors during PULL. Convert BoltConnectException to Neo4jException
109+
// so Session retry logic can detect and handle connection failures (triggers routing table refresh).
108110
try {
109111
$meta = $this->connection->pull($this->qid, $this->fetchSize);
110112
} catch (BoltConnectException $e) {
111113
// Close connection on socket errors
112114
$this->connection->invalidate();
115+
// Convert to Neo4jException with NotALeader code so Session.executeStatementWithRetry()
116+
// and Session.retry() can catch it and clear routing table for automatic failover recovery.
113117
throw new Neo4jException([Neo4jError::fromMessageAndCode('Neo.ClientError.Cluster.NotALeader', 'Connection error: '.$e->getMessage())], $e);
114118
}
115119

@@ -168,9 +172,10 @@ public function discard(): void
168172
try {
169173
$this->connection->discard($this->qid === -1 ? null : $this->qid);
170174
} catch (BoltConnectException $e) {
175+
// Connection already broken if DISCARD fails. Invalidate to prevent pool from reusing it.
176+
// Don't rethrow: this is called from __destruct() where exceptions don't propagate properly.
177+
// Connection will be detected as broken on next operation when pool tries to reuse it.
171178
$this->connection->invalidate();
172-
// Ignore connection errors during discard - connection is already broken
173-
// The Neo4jException will be thrown when the next operation is attempted
174179
}
175180
}
176181
}

src/Bolt/Session.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,29 @@ private function shouldClearRoutingTable(Neo4jException $e): bool
248248
*
249249
* @return SummarizedResult The result of the statement
250250
*/
251+
252+
/**
253+
* Execute instant transaction (session.run) with automatic retry on connection/routing errors.
254+
*
255+
* PURPOSE:
256+
* - Handles transient failures transparently to user: connection timeouts, server unavailable, etc.
257+
* - Supports cluster failover: when server goes down, clears routing table and retries on different node
258+
* - Distinguishes errors: retries on connection/routing issues but fails immediately on client errors (syntax, auth)
259+
* - Improves reliability: 3 retry attempts with fresh routing table each time = high availability
260+
*
261+
* EXAMPLE: User calls session.run("CREATE (n)") during cluster failover:
262+
* Attempt 1: Node A is leader → "NotALeader" (stepping down) → Clear routing table
263+
* Attempt 2: Node B elected leader → "Connection timeout" (election in progress) → Retry
264+
* Attempt 3: Cluster stable → Query succeeds
265+
* User sees: Query succeeded transparently (no exception, no manual retry needed)
266+
*
267+
* WHY THIS IS CRITICAL FOR DRIVERS:
268+
* - All Neo4j drivers (Java, Python, JavaScript) have this pattern
269+
* - Without it: user must manually retry or wrap every session.run() in try-catch
270+
* - With it: driver handles recovery automatically = better UX and reliability
271+
*/
251272
private function executeStatementWithRetry(Statement $statement, TransactionConfiguration $config): SummarizedResult
252-
{
273+
{ // Retry instant transactions up to 3 times on connection/routing errors; catch distinguishes retryable errors from client errors (syntax, auth) and clears routing table for cluster failover.
253274
$maxRetries = 3;
254275
$retries = 0;
255276

0 commit comments

Comments
 (0)