Skip to content

Commit

Permalink
update Swoole Library to v5.1.0
Browse files Browse the repository at this point in the history
Signed-off-by: Demin Yin <[email protected]>
  • Loading branch information
deminy committed Oct 5, 2023
1 parent 4b6e615 commit ca19f5f
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 91 deletions.
1 change: 1 addition & 0 deletions src/swoole_library/src/__init__.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
'core/Database/MysqliPool.php',
'core/Database/MysqliProxy.php',
'core/Database/MysqliStatementProxy.php',
'core/Database/DetectsLostConnections.php',
'core/Database/PDOConfig.php',
'core/Database/PDOPool.php',
'core/Database/PDOProxy.php',
Expand Down
2 changes: 1 addition & 1 deletion src/swoole_library/src/core/Coroutine/FastCGI/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public function __construct(string $host, int $port = 0, bool $ssl = false)
}

/**
* @return HttpResponse|Response
* @throws Exception
* @return HttpResponse|Response
*/
public function execute(Request $request, float $timeout = -1): Response
{
Expand Down
81 changes: 81 additions & 0 deletions src/swoole_library/src/core/Database/DetectsLostConnections.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
/**
* This file is part of Swoole.
*
* @link https://www.swoole.com
* @contact [email protected]
* @license https://github.com/swoole/library/blob/master/LICENSE
*/

declare(strict_types=1);

namespace Swoole\Database;

use Throwable;

class DetectsLostConnections
{
private const ERROR_MESSAGES = [
'server has gone away',
'no connection to the server',
'Lost connection',
'is dead or not enabled',
'Error while sending',
'decryption failed or bad record mac',
'server closed the connection unexpectedly',
'SSL connection has been closed unexpectedly',
'Error writing data to the connection',
'Resource deadlock avoided',
'Transaction() on null',
'child connection forced to terminate due to client_idle_limit',
'query_wait_timeout',
'reset by peer',
'Physical connection is not usable',
'TCP Provider: Error code 0x68',
'ORA-03113',
'ORA-03114',
'Packets out of order. Expected',
'Adaptive Server connection failed',
'Communication link failure',
'connection is no longer usable',
'Login timeout expired',
'SQLSTATE[HY000] [2002] Connection refused',
'running with the --read-only option so it cannot execute this statement',
'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',
'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for',
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',
'SQLSTATE[HY000] [2002] Connection timed out',
'SSL: Connection timed out',
'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',
'Temporary failure in name resolution',
'SSL: Broken pipe',
'SQLSTATE[08S01]: Communication link failure',
'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host',
'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host',
'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.',
'SQLSTATE[08006] [7] could not translate host name',
'TCP Provider: Error code 0x274C',
'SQLSTATE[HY000] [2002] No such file or directory',
'SSL: Operation timed out',
'Reason: Server is in script upgrade mode. Only administrator can connect at this time.',
'Unknown $curl_error_code: 77',
'SSL: Handshake timed out',
'SQLSTATE[08006] [7] SSL error: sslv3 alert unexpected message',
'SQLSTATE[08006] [7] unrecognized SSL error code:',
'SQLSTATE[HY000] [2002] No connection could be made because the target machine actively refused it',
];

public static function causedByLostConnection(Throwable $e): bool
{
$message = $e->getMessage();
foreach (self::ERROR_MESSAGES as $needle) {
if ($needle !== '' && mb_strpos($message, $needle) !== false) {
return true;
}
}

return false;
}
}
6 changes: 3 additions & 3 deletions src/swoole_library/src/core/Database/PDOConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class PDOConfig
/** @var int */
protected $port = 3306;

/** @var null|string */
protected $unixSocket;
/** @var string */
protected $unixSocket = '';

/** @var string */
protected $dbname = 'test';
Expand Down Expand Up @@ -71,7 +71,7 @@ public function getPort(): int

public function hasUnixSocket(): bool
{
return isset($this->unixSocket);
return !empty($this->unixSocket);
}

public function getUnixSocket(): string
Expand Down
60 changes: 43 additions & 17 deletions src/swoole_library/src/core/Database/PDOPool.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

namespace Swoole\Database;

use Exception;
use PDO;
use Swoole\ConnectionPool;

/**
* @method \PDO|PDOProxy get()
* @method void put(PDO|PDOProxy $connection)
*/
class PDOPool extends ConnectionPool
Expand All @@ -31,22 +31,48 @@ public function __construct(PDOConfig $config, int $size = self::DEFAULT_SIZE)
$this->config = $config;
parent::__construct(function () {
$driver = $this->config->getDriver();
return new \PDO(
"{$driver}:" .
(
$this->config->hasUnixSocket() ?
"unix_socket={$this->config->getUnixSocket()};" :
"host={$this->config->getHost()};port={$this->config->getPort()};"
) .
"dbname={$this->config->getDbname()};" .
(
($driver !== 'pgsql') ?
"charset={$this->config->getCharset()}" : ''
),
$this->config->getUsername(),
$this->config->getPassword(),
$this->config->getOptions()
);
if ($driver === 'sqlite') {
return new PDO($this->createDSN('sqlite'));
}

return new PDO($this->createDSN($driver), $this->config->getUsername(), $this->config->getPassword(), $this->config->getOptions());
}, $size, PDOProxy::class);
}

public function get(float $timeout = -1)
{
$pdo = parent::get($timeout);
/* @var \Swoole\Database\PDOProxy $pdo */
$pdo->reset();
return $pdo;
}

/**
* @purpose create DSN
* @throws Exception
*/
private function createDSN(string $driver): string
{
switch ($driver) {
case 'mysql':
if ($this->config->hasUnixSocket()) {
$dsn = "mysql:unix_socket={$this->config->getUnixSocket()};dbname={$this->config->getDbname()};charset={$this->config->getCharset()}";
} else {
$dsn = "mysql:host={$this->config->getHost()};port={$this->config->getPort()};dbname={$this->config->getDbname()};charset={$this->config->getCharset()}";
}
break;
case 'pgsql':
$dsn = 'pgsql:host=' . ($this->config->hasUnixSocket() ? $this->config->getUnixSocket() : $this->config->getHost()) . ";port={$this->config->getPort()};dbname={$this->config->getDbname()}";
break;
case 'oci':
$dsn = 'oci:dbname=' . ($this->config->hasUnixSocket() ? $this->config->getUnixSocket() : $this->config->getHost()) . ':' . $this->config->getPort() . '/' . $this->config->getDbname() . ';charset=' . $this->config->getCharset();
break;
case 'sqlite':
$dsn = 'sqlite:' . $this->config->getDbname();
break;
default:
throw new Exception('Unsupported Database Driver:' . $driver);
}
return $dsn;
}
}
72 changes: 35 additions & 37 deletions src/swoole_library/src/core/Database/PDOProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@

namespace Swoole\Database;

use PDO;
use PDOException;

class PDOProxy extends ObjectProxy
{
public const IO_ERRORS = [
2002, // MYSQLND_CR_CONNECTION_ERROR
2006, // MYSQLND_CR_SERVER_GONE_ERROR
2013, // MYSQLND_CR_SERVER_LOST
];

/** @var \PDO */
/** @var PDO */
protected $__object;

/** @var null|array */
Expand All @@ -31,46 +28,41 @@ class PDOProxy extends ObjectProxy
/** @var int */
protected $round = 0;

/** @var int */
protected $inTransaction = 0;

public function __construct(callable $constructor)
{
parent::__construct($constructor());
$this->__object->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
$this->__object->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->constructor = $constructor;
}

public function __call(string $name, array $arguments)
{
for ($n = 3; $n--;) {
$ret = @$this->__object->{$name}(...$arguments);
if ($ret === false) {
$errorInfo = $this->__object->errorInfo();
if (empty($errorInfo)) {
break;
}
/* no more chances or non-IO failures */
if (
!in_array($errorInfo[1], static::IO_ERRORS, true)
|| $n === 0
|| $this->__object->inTransaction()
) {
/* '00000' means “no error.”, as specified by ANSI SQL and ODBC. */
if (!empty($errorInfo) && $errorInfo[0] !== '00000') {
$exception = new \PDOException($errorInfo[2], $errorInfo[1]);
$exception->errorInfo = $errorInfo;
throw $exception;
}
/* no error info, just return false */
break;
}
try {
$ret = $this->__object->{$name}(...$arguments);
} catch (PDOException $e) {
if (!$this->__object->inTransaction() && DetectsLostConnections::causedByLostConnection($e)) {
$this->reconnect();
continue;
$ret = $this->__object->{$name}(...$arguments);
} else {
throw $e;
}
if ((strcasecmp($name, 'prepare') === 0) || (strcasecmp($name, 'query') === 0)) {
$ret = new PDOStatementProxy($ret, $this);
}
break;
}
/* @noinspection PhpUndefinedVariableInspection */

if (strcasecmp($name, 'beginTransaction') === 0) {
$this->inTransaction++;
}

if ((strcasecmp($name, 'commit') === 0 || strcasecmp($name, 'rollback') === 0) && $this->inTransaction > 0) {
$this->inTransaction--;
}

if ((strcasecmp($name, 'prepare') === 0) || (strcasecmp($name, 'query') === 0)) {
$ret = new PDOStatementProxy($ret, $this);
}

return $ret;
}

Expand All @@ -83,6 +75,7 @@ public function reconnect(): void
{
$constructor = $this->constructor;
parent::__construct($constructor());
$this->__object->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->round++;
/* restore context */
if ($this->setAttributeContext) {
Expand All @@ -100,6 +93,11 @@ public function setAttribute(int $attribute, $value): bool

public function inTransaction(): bool
{
return $this->__object->inTransaction();
return $this->inTransaction > 0;
}

public function reset(): void
{
$this->inTransaction = 0;
}
}
47 changes: 14 additions & 33 deletions src/swoole_library/src/core/Database/PDOStatementProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@

namespace Swoole\Database;

use PDOException;
use PDOStatement;

class PDOStatementProxy extends ObjectProxy
{
/** @var \PDOStatement */
/** @var PDOStatement */
protected $__object;

/** @var null|array */
Expand All @@ -37,7 +40,7 @@ class PDOStatementProxy extends ObjectProxy
/** @var int */
protected $parentRound;

public function __construct(\PDOStatement $object, PDOProxy $parent)
public function __construct(PDOStatement $object, PDOProxy $parent)
{
parent::__construct($object);
$this->parent = $parent;
Expand All @@ -46,40 +49,17 @@ public function __construct(\PDOStatement $object, PDOProxy $parent)

public function __call(string $name, array $arguments)
{
for ($n = 3; $n--;) {
$ret = @$this->__object->{$name}(...$arguments);
if ($ret === false) {
$errorInfo = $this->__object->errorInfo();
if (empty($errorInfo)) {
break;
}
/* no more chances or non-IO failures or in transaction */
if (
!in_array($errorInfo[1], $this->parent::IO_ERRORS, true)
|| $n === 0
|| $this->parent->inTransaction()
) {
/* '00000' means “no error.”, as specified by ANSI SQL and ODBC. */
if (!empty($errorInfo) && $errorInfo[0] !== '00000') {
$exception = new \PDOException($errorInfo[2], $errorInfo[1]);
$exception->errorInfo = $errorInfo;
throw $exception;
}
/* no error info, just return false */
break;
}
try {
$ret = $this->__object->{$name}(...$arguments);
} catch (PDOException $e) {
if (!$this->parent->inTransaction() && DetectsLostConnections::causedByLostConnection($e)) {
if ($this->parent->getRound() === $this->parentRound) {
/* if not equal, parent has reconnected */
$this->parent->reconnect();
}
$parent = $this->parent->__getObject();
$this->__object = $parent->prepare($this->__object->queryString);
if ($this->__object === false) {
$errorInfo = $parent->errorInfo();
$exception = new \PDOException($errorInfo[2], $errorInfo[1]);
$exception->errorInfo = $errorInfo;
throw $exception;
}

if ($this->setAttributeContext) {
foreach ($this->setAttributeContext as $attribute => $value) {
$this->__object->setAttribute($attribute, $value);
Expand All @@ -103,11 +83,12 @@ public function __call(string $name, array $arguments)
$this->__object->bindParam($value, ...$item);
}
}
continue;
$ret = $this->__object->{$name}(...$arguments);
} else {
throw $e;
}
break;
}
/* @noinspection PhpUndefinedVariableInspection */

return $ret;
}

Expand Down

0 comments on commit ca19f5f

Please sign in to comment.