Skip to content

Commit

Permalink
Merge pull request #8 from utopia-php/feat-retry-loop
Browse files Browse the repository at this point in the history
Feat: Retry loop
  • Loading branch information
eldadfux authored Nov 10, 2022
2 parents 7906cdb + d5dc421 commit 63cf2c3
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 6 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ $pool = new Pool('mysql-pool', 1 /* number of connections */, function() {
$pool->setReconnectAttempts(3); // number of attempts to reconnect
$pool->setReconnectSleep(5); // seconds to sleep between reconnect attempts

$pool->setRetryAttempts(3); // number of attempts to get connection
$pool->setRetrySleep(5); // seconds to sleep between failed pop-connection attempts

$connection = $pool->pop(); // Get a connection from the pool
$connection->getID(); // Get the connection ID
$connection->getResource(); // Get the connection resource
Expand All @@ -67,6 +70,17 @@ $group->setReconnectAttempts(3); // Set the number of reconnect attempts for all
$group->setReconnectSleep(5); // Set the sleep time between reconnect attempts for all pools
```

## Reconnect and Retry

Both reconnect and retry logic is used in `pop()` method to handle problematic scenarios. Both allow you to configure 2 properties:

- `attempts` - How many times library will retry when problem occurs
- `sleep` - How long will library wait for before next retry attempt

**Reconnect** settings are used when your connection initialization callback throws an exception. This can occur for example, when a handshake with SQL server fails.

**Retry** settings are used when pool of connection is empty and there is no more connections to pop. This can occur for example, when your server supports more concurrent actions than your pool.

## System Requirements

Utopia Framework requires PHP 8.0 or later. We recommend using the latest PHP version whenever possible.
Expand Down
63 changes: 57 additions & 6 deletions src/Pools/Pool.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ class Pool
*/
protected int $reconnectSleep = 1; // seconds

/**
* @var int
*/
protected int $retryAttempts = 3;

/**
* @var int
*/
protected int $retrySleep = 1; // seconds

/**
* @var array<Connection|true>
*/
Expand Down Expand Up @@ -106,6 +116,42 @@ public function setReconnectSleep(int $reconnectSleep): self
return $this;
}

/**
* @return int
*/
public function getRetryAttempts(): int
{
return $this->retryAttempts;
}

/**
* @param int $retryAttempts
* @return self
*/
public function setRetryAttempts(int $retryAttempts): self
{
$this->retryAttempts = $retryAttempts;
return $this;
}

/**
* @return int
*/
public function getRetrySleep(): int
{
return $this->retrySleep;
}

/**
* @param int $retrySleep
* @return self
*/
public function setRetrySleep(int $retrySleep): self
{
$this->retrySleep = $retrySleep;
return $this;
}

/**
* Summary:
* 1. Try to get a connection from the pool
Expand All @@ -117,17 +163,22 @@ public function setReconnectSleep(int $reconnectSleep): self
*/
public function pop(): Connection
{
$connection = array_pop($this->pool);

if (is_null($connection)) { // pool is empty, wait an if still empty throw exception
usleep(50000); // 50ms TODO: make this configurable
$attempts = 0;

do {
$attempts++;
$connection = array_pop($this->pool);

if (is_null($connection)) {
throw new Exception('Pool is empty');
if ($attempts >= $this->getRetryAttempts()) {
throw new Exception('Pool is empty');
}

sleep($this->getRetrySleep());
} else {
break;
}
}
} while ($attempts < $this->getRetryAttempts());

if ($connection === true) { // Pool has space, create connection
$attempts = 0;
Expand Down
51 changes: 51 additions & 0 deletions tests/Pools/PoolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,34 @@ public function testSetReconnectSleep(): void
$this->assertEquals(20, $this->object->getReconnectSleep());
}

public function testGetRetryAttempts(): void
{
$this->assertEquals(3, $this->object->getRetryAttempts());
}

public function testSetRetryAttempts(): void
{
$this->assertEquals(3, $this->object->getRetryAttempts());

$this->object->setRetryAttempts(20);

$this->assertEquals(20, $this->object->getRetryAttempts());
}

public function testGetRetrySleep(): void
{
$this->assertEquals(1, $this->object->getRetrySleep());
}

public function testSetRetrySleep(): void
{
$this->assertEquals(1, $this->object->getRetrySleep());

$this->object->setRetrySleep(20);

$this->assertEquals(20, $this->object->getRetrySleep());
}

public function testPop(): void
{
$this->assertEquals(5, $this->object->count());
Expand Down Expand Up @@ -164,4 +192,27 @@ public function testIsFull(): void

$this->assertEquals(false, $this->object->isFull());
}

public function testRetry(): void
{
$this->object->setReconnectAttempts(2);
$this->object->setReconnectSleep(2);

$this->object->pop();
$this->object->pop();
$this->object->pop();
$this->object->pop();
$this->object->pop();

// Pool should be empty
$this->expectException(Exception::class);

$timeStart = \time();
$this->object->pop();
$timeEnd = \time();

$timeDiff = $timeEnd - $timeStart;

$this->assertGreaterThanOrEqual(4, $timeDiff);
}
}

0 comments on commit 63cf2c3

Please sign in to comment.