@@ -134,6 +134,8 @@ public function acquire(SessionConfiguration $config): Generator
134134 $ latestError = null ;
135135
136136 if ($ table == null ) {
137+ $ this ->getLogger ()?->log(LogLevel::DEBUG , 'Routing table not found in cache, fetching new routing table ' );
138+
137139 $ addresses = $ this ->getAddresses ($ this ->data ->getUri ()->getHost ());
138140 foreach ($ addresses as $ address ) {
139141 $ triedAddresses [] = $ address ;
@@ -150,8 +152,18 @@ public function acquire(SessionConfiguration $config): Generator
150152 */
151153 $ connection = GeneratorHelper::getReturnFromGenerator ($ pool ->acquire ($ config ));
152154 $ table = $ this ->routingTable ($ connection , $ config );
155+
156+ $ this ->getLogger ()?->log(LogLevel::DEBUG , 'Successfully fetched routing table ' , [
157+ 'ttl ' => $ table ->getTtl (),
158+ 'leaders ' => $ table ->getWithRole (RoutingRoles::LEADER ()),
159+ 'followers ' => $ table ->getWithRole (RoutingRoles::FOLLOWER ()),
160+ 'routers ' => $ table ->getWithRole (RoutingRoles::ROUTE ()),
161+ ]);
153162 } catch (ConnectException $ e ) {
154- // todo - once client side logging is implemented it must be conveyed here.
163+ $ this ->getLogger ()?->log(LogLevel::WARNING , 'Failed to connect to address ' , [
164+ 'address ' => $ address ,
165+ 'error ' => $ e ->getMessage (),
166+ ]);
155167 $ latestError = $ e ;
156168 continue ; // We continue if something is wrong with the current server
157169 }
@@ -174,6 +186,11 @@ public function acquire(SessionConfiguration $config): Generator
174186 $ server = $ server ->withScheme ($ this ->data ->getUri ()->getScheme ());
175187 }
176188
189+ $ this ->getLogger ()?->log(LogLevel::DEBUG , 'Acquiring connection from server ' , [
190+ 'server ' => (string ) $ server ,
191+ 'access_mode ' => $ config ->getAccessMode ()?->getValue(),
192+ ]);
193+
177194 return $ this ->createOrGetPool ($ this ->data ->getUri ()->getHost (), $ server )->acquire ($ config );
178195 }
179196
@@ -182,6 +199,89 @@ public function getLogger(): ?Neo4jLogger
182199 return $ this ->logger ;
183200 }
184201
202+ /**
203+ * Get the current routing table from cache for the given session configuration.
204+ *
205+ * @return RoutingTable|null The cached routing table, or null if not yet initialized
206+ */
207+ public function getRoutingTable (SessionConfiguration $ config ): ?RoutingTable
208+ {
209+ $ key = $ this ->createKey ($ this ->data , $ config );
210+ /** @var RoutingTable|null $table */
211+ $ table = $ this ->cache ->get ($ key );
212+
213+ return $ table ;
214+ }
215+
216+ /**
217+ * Clear the cached routing table for the given session configuration.
218+ * This forces a new routing table to be fetched on the next acquire() call.
219+ */
220+ public function clearRoutingTable (SessionConfiguration $ config ): void
221+ {
222+ $ key = $ this ->createKey ($ this ->data , $ config );
223+ $ deleted = $ this ->cache ->delete ($ key );
224+
225+ $ this ->getLogger ()?->log(LogLevel::INFO , 'Cleared routing table from cache ' , [
226+ 'key ' => $ key ,
227+ 'deleted ' => $ deleted ,
228+ ]);
229+ }
230+
231+ /**
232+ * Remove a failed server from the routing table.
233+ * This removes the server from all roles (leader, follower, router) and updates the cache.
234+ *
235+ * @param SessionConfiguration $config The session configuration
236+ * @param string $serverAddress The address of the failed server (e.g., "172.18.0.3:9010")
237+ */
238+ public function removeFailedServer (SessionConfiguration $ config , string $ serverAddress ): void
239+ {
240+ $ key = $ this ->createKey ($ this ->data , $ config );
241+ /** @var RoutingTable|null $table */
242+ $ table = $ this ->cache ->get ($ key );
243+
244+ if ($ table !== null ) {
245+ $ this ->getLogger ()?->log(LogLevel::WARNING , 'Removing failed server from routing table ' , [
246+ 'server ' => $ serverAddress ,
247+ ]);
248+
249+ // Remove the server and update cache
250+ $ updatedTable = $ table ->removeServer ($ serverAddress );
251+
252+ // Only update cache if the table actually changed
253+ if ($ updatedTable !== $ table ) {
254+ $ this ->cache ->set ($ key , $ updatedTable , $ updatedTable ->getTtl ());
255+
256+ $ this ->getLogger ()?->log(LogLevel::INFO , 'Updated routing table after removing failed server ' , [
257+ 'server ' => $ serverAddress ,
258+ 'remaining_leaders ' => $ updatedTable ->getWithRole (RoutingRoles::LEADER ()),
259+ 'remaining_followers ' => $ updatedTable ->getWithRole (RoutingRoles::FOLLOWER ()),
260+ 'remaining_routers ' => $ updatedTable ->getWithRole (RoutingRoles::ROUTE ()),
261+ ]);
262+ }
263+ }
264+ }
265+
266+ /**
267+ * Check if a server exists in the routing table.
268+ *
269+ * @param SessionConfiguration $config The session configuration
270+ * @param string $serverAddress The address of the server to check
271+ *
272+ * @return bool True if the server exists in the routing table, false otherwise
273+ */
274+ public function hasServer (SessionConfiguration $ config , string $ serverAddress ): bool
275+ {
276+ $ table = $ this ->getRoutingTable ($ config );
277+
278+ if ($ table === null ) {
279+ return false ;
280+ }
281+
282+ return $ table ->hasServer ($ serverAddress );
283+ }
284+
185285 /**
186286 * @throws Exception
187287 */
@@ -193,6 +293,10 @@ private function getNextServer(RoutingTable $table, ?AccessMode $mode): Uri
193293 $ servers = $ table ->getWithRole (RoutingRoles::FOLLOWER ());
194294 }
195295
296+ if (count ($ servers ) === 0 ) {
297+ throw new RuntimeException (sprintf ('No servers available for access mode: %s ' , $ mode ?->getValue() ?? 'WRITE ' ));
298+ }
299+
196300 return Uri::create ($ servers [random_int (0 , count ($ servers ) - 1 )]);
197301 }
198302
@@ -252,6 +356,8 @@ private function createKey(ConnectionRequestData $data, ?SessionConfiguration $c
252356
253357 public function close (): void
254358 {
359+ $ this ->getLogger ()?->log(LogLevel::INFO , 'Closing all connection pools ' );
360+
255361 foreach (self ::$ pools as $ pool ) {
256362 $ pool ->close ();
257363 }
0 commit comments