@@ -311,14 +311,15 @@ public function delete($url, array $customHeaders = [], $data = '')
311
311
return $ this ->parseResponse ($ response );
312
312
});
313
313
}
314
-
314
+
315
315
/**
316
316
* Execute the specified callback, and try again if it fails because
317
317
* the target server is not available. In this case, try again with failing
318
318
* over to an alternative server (the new leader) until the maximum number
319
319
* of failover attempts have been made
320
320
*
321
- * @throws Exception
321
+ * @throws Exception - either a ConnectException or a FailoverException or an
322
+ * Exception produced by the callback function
322
323
*
323
324
* @param mixed $cb - the callback function to execute
324
325
*
@@ -331,40 +332,74 @@ private function handleFailover($cb)
331
332
return $ cb ();
332
333
}
333
334
334
- // now with failover
335
- $ tried = [ ];
335
+ // here we need to try it with failover
336
+ $ start = microtime (true );
337
+ $ tried = [];
338
+ $ notReachable = [];
336
339
while (true ) {
340
+ $ ep = $ this ->_options ->getCurrentEndpoint ();
341
+ $ normalized = Endpoint::normalizeHostname ($ ep );
342
+
343
+ if (isset ($ notReachable [$ normalized ])) {
344
+ $ this ->notify ('no more servers available to try connecting to ' );
345
+ throw new ConnectException ('no more servers available to try connecting to ' );
346
+ }
347
+
337
348
try {
338
- // mark the endpoint as being used
339
- $ tried [$ this -> _options -> getCurrentEndpoint () ] = true ;
349
+ // mark the endpoint as being tried
350
+ $ tried [$ normalized ] = true ;
340
351
return $ cb ();
341
352
} catch (ConnectException $ e ) {
342
353
// could not connect. now try again with a different server if possible
343
- if (count ($ tried ) > $ this ->_options [ConnectionOptions::OPTION_FAILOVER_TRIES ]) {
354
+ if ($ this ->_options [ConnectionOptions::OPTION_FAILOVER_TRIES ] > 0 &&
355
+ count ($ tried ) > $ this ->_options [ConnectionOptions::OPTION_FAILOVER_TRIES ]) {
356
+ $ this ->notify ('tried too many different servers in failover ' );
344
357
throw $ e ;
345
358
}
359
+ // mark endpoint as unreachable
360
+ $ notReachable [$ normalized ] = true ;
361
+
362
+ // move on to next endpoint
346
363
$ ep = $ this ->_options ->nextEndpoint ();
347
- if (isset ($ tried [$ ep ])) {
348
- // endpoint should have changed by failover procedure
349
- // if not, we can abort now
350
- throw $ e ;
364
+ $ normalized = Endpoint::normalizeHostname ($ ep );
365
+
366
+ if (isset ($ tried [$ normalized ])) {
367
+ if (microtime (true ) - $ start >= $ this ->_options [ConnectionOptions::OPTION_FAILOVER_TIMEOUT ]) {
368
+ // timeout reached, we will abort now
369
+ $ this ->notify ('no servers reachable after failover timeout ' );
370
+ throw $ e ;
371
+ }
372
+ // continue because we have not yet reached the timeout
373
+ usleep (20 * 1000 );
351
374
}
352
375
} catch (FailoverException $ e ) {
353
376
// got a failover. now try again with a different server if possible
354
- if (count ($ tried ) > $ this ->_options [ConnectionOptions::OPTION_FAILOVER_TRIES ]) {
377
+ // endpoint should have changed by failover procedure
378
+ $ ep = $ this ->_options ->getCurrentEndpoint ();
379
+ $ normalized = Endpoint::normalizeHostname ($ ep );
380
+
381
+ if ($ this ->_options [ConnectionOptions::OPTION_FAILOVER_TRIES ] > 0 &&
382
+ count ($ tried ) > $ this ->_options [ConnectionOptions::OPTION_FAILOVER_TRIES ]) {
383
+ $ this ->notify ('tried too many different servers in failover ' );
355
384
throw $ e ;
356
385
}
357
- if (isset ($ tried [$ this ->_options ->getCurrentEndpoint ()])) {
358
- // endpoint should have changed by failover procedure
359
- // if not, we can abort now
360
- throw $ e ;
386
+ if (isset ($ tried [$ normalized ])) {
387
+ if (microtime (true ) - $ start >= $ this ->_options [ConnectionOptions::OPTION_FAILOVER_TIMEOUT ]) {
388
+ // timeout reached, we can abort now
389
+ $ this ->notify ('no servers reachable after failover timeout ' );
390
+ throw $ e ;
391
+ }
392
+ // continue because we have not yet reached the timeout
393
+ usleep (20 * 1000 );
361
394
}
395
+
396
+ // we need to try the recommended leader again
397
+ unset($ notReachable [$ normalized ]);
362
398
}
363
399
// let all other exception types escape from here
364
400
}
365
401
}
366
402
367
-
368
403
/**
369
404
* Recalculate the static HTTP header string used for all HTTP requests in this connection
370
405
*/
@@ -616,7 +651,14 @@ public function parseResponse(HttpResponse $response)
616
651
617
652
// handle failover
618
653
if (is_array ($ details ) && isset ($ details ['errorNum ' ])) {
654
+ if ($ details ['errorNum ' ] === 1495 ) {
655
+ // 1495 = leadership challenge is ongoing
656
+ $ exception = new FailoverException (@$ details ['errorMessage ' ], @$ details ['code ' ]);
657
+ throw $ exception ;
658
+ }
659
+
619
660
if ($ details ['errorNum ' ] === 1496 ) {
661
+ // 1496 = not a leader
620
662
// not a leader. now try to find new leader
621
663
$ leader = $ response ->getLeaderEndpointHeader ();
622
664
if ($ leader ) {
@@ -866,8 +908,6 @@ public function setDatabase($database)
866
908
}
867
909
868
910
/**
869
- * Get the database that is currently used with this connection
870
- *
871
911
* Get the database to use with this connection, for example: 'my_database'
872
912
*
873
913
* @return string
@@ -876,6 +916,49 @@ public function getDatabase()
876
916
{
877
917
return $ this ->_database ;
878
918
}
919
+
920
+ /**
921
+ * Test if a connection can be made using the specified connection options,
922
+ * i.e. endpoint (host/port), username, password
923
+ *
924
+ * @return bool - true if the connection succeeds, false if not
925
+ */
926
+ public function test ()
927
+ {
928
+ try {
929
+ $ this ->get (Urls::URL_ADMIN_VERSION );
930
+ } catch (ConnectException $ e ) {
931
+ return false ;
932
+ } catch (ServerException $ e ) {
933
+ return false ;
934
+ } catch (ClientException $ e ) {
935
+ return false ;
936
+ }
937
+ return true ;
938
+ }
939
+
940
+ /**
941
+ * Returns the current endpoint we are currently connected to
942
+ * (or, if no connection has been established yet, the next endpoint
943
+ * that we will connect to)
944
+ *
945
+ * @return string - the current endpoint that we are connected to
946
+ */
947
+ public function getCurrentEndpoint ()
948
+ {
949
+ return $ this ->_options ->getCurrentEndpoint ();
950
+ }
951
+
952
+ /**
953
+ * Calls the notification callback function to inform to make the
954
+ * client application aware of some failure so it can do something
955
+ * appropriate (e.g. logging)
956
+ *
957
+ * @return void
958
+ */
959
+ private function notify ($ message ) {
960
+ $ this ->_options [ConnectionOptions::OPTION_NOTIFY_CALLBACK ]($ message );
961
+ }
879
962
}
880
963
881
964
class_alias (Connection::class, '\triagens\ArangoDb\Connection ' );
0 commit comments