Skip to content

Commit

Permalink
Merge b8f18e6 into 4ba75b2
Browse files Browse the repository at this point in the history
  • Loading branch information
maxiloc committed Dec 7, 2016
2 parents 4ba75b2 + b8f18e6 commit a8d4d65
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 12 deletions.
27 changes: 18 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
language: php

php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm

sudo: false
matrix:
include:
- php: 5.3
- php: 5.4
dist: trusty
sudo: required
- php: 5.5
dist: trusty
sudo: required
- php: 5.6
dist: trusty
sudo: required
- php: 7.0
dist: trusty
sudo: required
- php: hhvm
dist: trusty
sudo: required

cache:
directories:
Expand Down
5 changes: 4 additions & 1 deletion src/AlgoliaSearch/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public function __destruct()
}

/**
* Change the default connect timeout of 2s to a custom value
* Change the default connect timeout of 1s to a custom value
* (only useful if your server has a very slow connectivity to Algolia backend).
*
* @param int $connectTimeout the connection timeout
Expand Down Expand Up @@ -743,6 +743,9 @@ public function request(
throw $e;
} catch (\Exception $e) {
$exceptions[$host] = $e->getMessage();
if ($context instanceof ClientContext) {
$context->rotateHosts($host);
}
}
}
throw new AlgoliaException('Hosts unreachable: '.implode(',', $exceptions));
Expand Down
50 changes: 48 additions & 2 deletions src/AlgoliaSearch/ClientContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class ClientContext
*/
public $connectTimeout;

/**
* @var array
*/
private static $failingHosts = [];

/**
* ClientContext constructor.
*
Expand All @@ -88,8 +93,8 @@ class ClientContext
*/
public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled = false)
{
// connect timeout of 2s by default
$this->connectTimeout = 2;
// connect timeout of 1s by default
$this->connectTimeout = 1;

// global timeout of 30s by default
$this->readTimeout = 30;
Expand All @@ -99,6 +104,7 @@ public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled

$this->applicationID = $applicationID;
$this->apiKey = $apiKey;

$this->readHostsArray = $hostsArray;
$this->writeHostsArray = $hostsArray;

Expand All @@ -107,6 +113,8 @@ public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled
$this->writeHostsArray = $this->getDefaultWriteHosts();
}

$this->rotateHosts();

if ($this->applicationID == null || mb_strlen($this->applicationID) == 0) {
throw new Exception('AlgoliaSearch requires an applicationID.');
}
Expand Down Expand Up @@ -248,4 +256,42 @@ public function setExtraHeader($key, $value)
{
$this->headers[$key] = $value;
}

/**
* @param $host
*/
public function addFailingHost($host)
{
if (! in_array($host, self::$failingHosts)) {
self::$failingHosts[] = $host;
}
}

/**
* This method is called to pass on failing hosts.
* If a failingHost is passed we add it to the failingHosts array
* If the host is first either in the failingHosts array, we
* rotate the array to ensure the next API call will be directly made with a working
* host. This mainly ensures we don't add the equivalent of the connection timeout value to each
* request to the API.
* @param null $failingHost
*/
public function rotateHosts($failingHost = null)
{
if ($failingHost !== null) {
$this->addFailingHost($failingHost);
}

$i = 0;
while ($i <= count($this->readHostsArray) && in_array($this->readHostsArray[0], self::$failingHosts)) {
$i++;
$this->readHostsArray[] = array_shift($this->readHostsArray);
}

$i = 0;
while ($i <= count($this->writeHostsArray) && in_array($this->writeHostsArray[0], self::$failingHosts)) {
$i++;
$this->writeHostsArray[] = array_shift($this->writeHostsArray);
}
}
}
41 changes: 41 additions & 0 deletions tests/AlgoliaSearch/Tests/AccessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,45 @@ public function testAccessWithOptions()

$client->isAlive();
}

private function newClientWithUnresolvableHost()
{
return new Client(
getenv('ALGOLIA_APPLICATION_ID'),
getenv('ALGOLIA_API_KEY'),
array(
getenv('ALGOLIA_APPLICATION_ID') . '.algolia.biz', // .biz will always fail to resolve
getenv('ALGOLIA_APPLICATION_ID') . '.algolia.net'
)
);
}

public function testStatefullRetryStrategy()
{
if (version_compare(phpversion(), '5.4', '<')) {
$this->markTestSkipped("No way to test statefull retry strategy in Travis for PHP 5.3.");
}

$client = $this->newClientWithUnresolvableHost();
$start = microtime(true);
for ($i = 0; $i < 10; $i++) {
$client->isAlive();
}
$processingTime = microtime(true) - $start;
$this->assertLessThan(5, $processingTime);
}

public function testStatefullRetryStrategyForSeveralInstance()
{
$start = microtime(true);

for ($i = 0; $i < 10; $i++) {
$client = $this->newClientWithUnresolvableHost();
$client->isAlive();
}

$processingTime = microtime(true) - $start;
// Total processing should be between 5 seconds
$this->assertLessThanOrEqual(5, round($processingTime));
}
}
34 changes: 34 additions & 0 deletions tests/AlgoliaSearch/Tests/ClientContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,38 @@ public function testRandomWriteFallbackHosts()
);
$this->assertEquals($expectedHosts, $hosts);
}

public function testHostsCanBeRotated()
{
$context = new ClientContext('whatever', 'whatever', null);
$initialReadHosts = array('host1.com', 'shared-host.com', 'host3.com');
$initialWriteHosts = array('write-host1.com', 'shared-host.com', 'write-host3.com');
$context->readHostsArray = $initialReadHosts;
$context->writeHostsArray = $initialWriteHosts;

// Rotate read host.
$context->rotateHosts('host1.com');
$this->assertEquals(array('shared-host.com', 'host3.com', 'host1.com'), $context->readHostsArray);
$this->assertEquals($initialWriteHosts, $context->writeHostsArray);

// This should change nothing given the current host is shared-host.com.
$context->rotateHosts('host3.com');
$this->assertEquals(array('shared-host.com', 'host3.com', 'host1.com'), $context->readHostsArray);
$this->assertEquals($initialWriteHosts, $context->writeHostsArray);

// Rotate read write.
$context->rotateHosts('write-host1.com');
$this->assertEquals(array('shared-host.com', 'host3.com', 'host1.com'), $context->readHostsArray);
$this->assertEquals(array('shared-host.com', 'write-host3.com', 'write-host1.com'), $context->writeHostsArray);

// This should change nothing given the current host is shared-host.com.
$context->rotateHosts('write-host3.com');
$this->assertEquals(array('shared-host.com', 'host3.com', 'host1.com'), $context->readHostsArray);
$this->assertEquals(array('shared-host.com', 'write-host3.com', 'write-host1.com'), $context->writeHostsArray);

// Should rotate both read and write hosts.
$context->rotateHosts('shared-host.com');
$this->assertEquals(array('host3.com', 'host1.com', 'shared-host.com'), $context->readHostsArray);
$this->assertEquals(array('write-host3.com', 'write-host1.com', 'shared-host.com'), $context->writeHostsArray);
}
}

0 comments on commit a8d4d65

Please sign in to comment.