Class: Neo4j::Driver::Routing::RoutedConnection
- Inherits:
-
Object
- Object
- Neo4j::Driver::Routing::RoutedConnection
- Extended by:
- Forwardable
- Defined in:
- lib/neo4j/driver/routing/routed_connection.rb
Overview
Bolt::Connection wrapper that classifies routing-relevant errors and feeds the routing table back. Mirrors Java's RoutedBoltConnection + RoutingErrorHandler.onWriteFailure / onConnectionFailure.
The wrapper is a pure delegator on the read/write path — every
method is forwarded straight to the inner Bolt::Connection.
Classification doesn't happen on the wire-call boundary because
Bolt failures only become exceptions when the caller does
.assert_success! on the response (which raises outside the
delegator), and streaming failures only become exceptions when
Result's visitor dispatches to on_failure. Both of those call
sites — plus session.rb / transaction.rb's wire-error rescues —
invoke classify_failure(error) explicitly to run the side
effects (deactivate / on_write_failure) and get the
(possibly-swapped) exception to re-raise.
Constant Summary collapse
- WRITE_FAILURE_CODES =
Server-side error codes that indicate the connected node is no longer the leader for the database we wrote to. Surface is narrow on purpose — anything else (constraint violations, syntax errors, etc.) is a user-facing failure that mustn't invalidate the routing table.
%w[ Neo.ClientError.Cluster.NotALeader Neo.ClientError.General.ForbiddenOnReadOnlyDatabase ].freeze
- DATABASE_UNAVAILABLE_CODE =
'Neo.TransientError.General.DatabaseUnavailable'
Instance Attribute Summary collapse
-
#address_obj ⇒ Object
readonly
Returns the value of attribute address_obj.
-
#discard_on_release ⇒ Object
Returns the value of attribute discard_on_release.
-
#inner ⇒ Object
readonly
Returns the value of attribute inner.
Instance Method Summary collapse
-
#classify_failure(error) ⇒ Object
Run a caught exception through the routing classifier: - ServiceUnavailableException → deactivate(address); mark the connection discardable; return the same exception.
-
#initialize(pool, inner, address_obj, access_mode, database) ⇒ RoutedConnection
constructor
A new instance of RoutedConnection.
Constructor Details
#initialize(pool, inner, address_obj, access_mode, database) ⇒ RoutedConnection
Returns a new instance of RoutedConnection.
40 41 42 43 44 45 46 47 |
# File 'lib/neo4j/driver/routing/routed_connection.rb', line 40 def initialize(pool, inner, address_obj, access_mode, database) @pool = pool @inner = inner @address_obj = address_obj @access_mode = access_mode @database = database @discard_on_release = false end |
Instance Attribute Details
#address_obj ⇒ Object (readonly)
Returns the value of attribute address_obj.
37 38 39 |
# File 'lib/neo4j/driver/routing/routed_connection.rb', line 37 def address_obj @address_obj end |
#discard_on_release ⇒ Object
Returns the value of attribute discard_on_release.
38 39 40 |
# File 'lib/neo4j/driver/routing/routed_connection.rb', line 38 def discard_on_release @discard_on_release end |
#inner ⇒ Object (readonly)
Returns the value of attribute inner.
37 38 39 |
# File 'lib/neo4j/driver/routing/routed_connection.rb', line 37 def inner @inner end |
Instance Method Details
#classify_failure(error) ⇒ Object
Run a caught exception through the routing classifier:
- ServiceUnavailableException → deactivate(address); mark the connection discardable; return the same exception.
- TransientException with DatabaseUnavailable → same.
- ClientException with NotALeader / ForbiddenOnReadOnly on a WRITE → on_write_failure(address, database) to forget the writer (forces a re-route); the connection is NOT discarded (the node is fine, just not the leader) — RESET on release makes it reusable. Returns a SessionExpiredException (so session retry catches it) with the original error code preserved.
- Anything else → return the same exception unchanged.
Callers: session.rb (RUN-response rescue), transaction.rb (BEGIN/RUN/COMMIT rescues), Result#on_failure (streaming PULL/DISCARD). Each invokes this in its catch-Neo4jException block and re-raises whatever we return.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/neo4j/driver/routing/routed_connection.rb', line 74 def classify_failure(error) # Funnel security failures to the auth-token manager first (via # the inner connection's provider-set handler); a retryable one # comes back wrapped as SecurityRetryableException. Then apply # routing classification to whatever we surface. error = @inner.notify_security_exception(error) # A security failure compromises the connection (server closes # it; identity is bad) — discard regardless of routing role. @discard_on_release = true if error.is_a?(Exceptions::SecurityException) case error when Exceptions::SessionExpiredException # Already routing-classified — deactivate and keep the type. @discard_on_release = true @pool.deactivate(@address_obj) error when Exceptions::ServiceUnavailableException @discard_on_release = true @pool.deactivate(@address_obj) # In routing, a dead connection means this server can no longer # serve the session — surface as SessionExpired so managed-tx # retry picks a different server (mirrors Java's RoutingConnection # mapping connection failures to SessionExpired). Both are # retryable, so retry behaviour is unchanged; only the surfaced # type differs. The original is chained as `cause` automatically # when the caller re-raises this inside its `rescue => error`. Exceptions::SessionExpiredException.new(error., code: error.code) when Exceptions::TransientException if error.code == DATABASE_UNAVAILABLE_CODE @discard_on_release = true @pool.deactivate(@address_obj) end error when Exceptions::ClientException if @access_mode == :write && WRITE_FAILURE_CODES.include?(error.code) # NotALeader / ForbiddenOnReadOnlyDatabase: the node is no longer # the leader, but the connection itself is fine. Forget it as a # writer (forces a re-route) but DON'T discard it — RESET on # release (Bolt::Pool#push) clears the FAILED state so the retry # can reuse it (matches Java's onWriteFailure, which keeps the # connection; testkit's retry_with_fail_after_pull reuses it). @pool.on_write_failure(@address_obj, @database) # Original ClientException chained as `cause` at re-raise. Exceptions::SessionExpiredException.new( "Server at #{@address_obj} no longer accepts writes for database " \ "#{@database.inspect} (#{error.code})", code: error.code ) else error end else error end end |