Skip to content

Commit

Permalink
Add close() method to force-close the connection
Browse files Browse the repository at this point in the history
  • Loading branch information
clue committed Sep 6, 2018
1 parent e7ff4c1 commit 2f77291
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 8 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ It is written in pure PHP and does not require any extensions.
* [query()](#query)
* [queryStream()](#querystream)
* [ping()](#ping)
* [quit()](#quit)
* [close()](#close)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
Expand Down Expand Up @@ -275,6 +277,22 @@ $connection->query('CREATE TABLE test ...');
$connection->quit();
```

#### close()

The `close(): void` method can be used to
force-close the connection.

Unlike the `quit()` method, this method will immediately force-close the
connection and reject all oustanding commands.

```php
$connection->close();
```

Forcefully closing the connection will yield a warning in the server logs
and should generally only be used as a last resort. See also
[`quit()`](#quit) as a safe alternative.

## Install

The recommended way to install this library is [through Composer](https://getcomposer.org).
Expand Down
18 changes: 18 additions & 0 deletions src/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,22 @@ public function ping();
* @return PromiseInterface Returns a Promise<void,Exception>
*/
public function quit();

/**
* Force-close the connection.
*
* Unlike the `quit()` method, this method will immediately force-close the
* connection and reject all oustanding commands.
*
* ```php
* $connection->close();
* ```
*
* Forcefully closing the connection will yield a warning in the server logs
* and should generally only be used as a last resort. See also
* [`quit()`](#quit) as a safe alternative.
*
* @return void
*/
public function close();
}
32 changes: 24 additions & 8 deletions src/Io/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class Connection extends EventEmitter implements ConnectionInterface
*/
private $stream;

private $closed = false;

/**
* Connection constructor.
*
Expand Down Expand Up @@ -167,6 +169,27 @@ public function quit()
});
}

public function close()
{
if ($this->state === self::STATE_CLOSED) {
return;
}

$this->state = self::STATE_CLOSED;
$this->stream->close();

// reject all pending commands if connection is closed
while (!$this->executor->isIdle()) {
$command = $this->executor->dequeue();
$command->emit('error', array(
new \RuntimeException('Connection lost')
));
}

$this->emit('close');
$this->removeAllListeners();
}

/**
* @param Exception $err Error from socket.
*
Expand All @@ -185,17 +208,10 @@ public function handleConnectionError($err)
public function handleConnectionClosed()
{
if ($this->state < self::STATE_CLOSEING) {
$this->state = self::STATE_CLOSED;
$this->emit('error', [new \RuntimeException('mysql server has gone away'), $this]);
}

// reject all pending commands if connection is closed
while (!$this->executor->isIdle()) {
$command = $this->executor->dequeue();
$command->emit('error', array(
new \RuntimeException('Connection lost')
));
}
$this->close();
}

/**
Expand Down
53 changes: 53 additions & 0 deletions tests/FactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,57 @@ public function testConnectWithValidAuthQuitOnlyOnce()

$loop->run();
}

public function testConnectWithValidAuthCanCloseOnlyOnce()
{
$this->expectOutputString('connected.closed.');

$loop = \React\EventLoop\Factory::create();
$factory = new Factory($loop);

$uri = $this->getConnectionString();
$factory->createConnection($uri)->then(function (ConnectionInterface $connection) {
echo 'connected.';
$connection->on('close', function () {
echo 'closed.';
});
$connection->on('error', function () {
echo 'error?';
});

$connection->close();
$connection->close();
}, 'printf')->then(null, 'printf');

$loop->run();
}

public function testConnectWithValidAuthCanCloseAndAbortPing()
{
$this->expectOutputString('connected.aborted pending (Connection lost).aborted queued (Connection lost).closed.');

$loop = \React\EventLoop\Factory::create();
$factory = new Factory($loop);

$uri = $this->getConnectionString();
$factory->createConnection($uri)->then(function (ConnectionInterface $connection) {
echo 'connected.';
$connection->on('close', function () {
echo 'closed.';
});
$connection->on('error', function () {
echo 'error?';
});

$connection->ping()->then(null, function ($e) {
echo 'aborted pending (' . $e->getMessage() .').';
});
$connection->ping()->then(null, function ($e) {
echo 'aborted queued (' . $e->getMessage() . ').';
});
$connection->close();
}, 'printf')->then(null, 'printf');

$loop->run();
}
}

0 comments on commit 2f77291

Please sign in to comment.