Skip to content

Commit

Permalink
merge changes from MR #163 to branch 5.1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
deminy committed Dec 24, 2023
1 parent e99c29b commit dd9ca3b
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 100 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ jobs:
docker-compose exec -T app composer install -n
- name: Run Unit Tests
run: docker-compose exec -T app composer test
run: |
sleep 40s
docker-compose exec -T app composer test
- name: Stop and Remove Docker Containers
run: docker-compose down
48 changes: 40 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
FROM phpswoole/swoole

RUN pecl update-channels
RUN docker-php-ext-enable redis
RUN docker-php-ext-install mysqli
RUN docker-php-ext-enable mysqli
RUN docker-php-ext-install pdo_mysql
RUN docker-php-ext-enable pdo_mysql

RUN echo "swoole.enable_library=off" >> /usr/local/etc/php/conf.d/docker-php-ext-swoole.ini && \
RUN apt update \
&& apt install -y libaio-dev libc-ares-dev libaio1 supervisor wget git \
&& wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip \
&& unzip instantclient-basiclite-linuxx64.zip && rm instantclient-basiclite-linuxx64.zip \
&& wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-sdk-linuxx64.zip \
&& unzip instantclient-sdk-linuxx64.zip && rm instantclient-sdk-linuxx64.zip \
&& mv instantclient_*_* ./instantclient \
&& rm ./instantclient/sdk/include/ldap.h \
&& echo DISABLE_INTERRUPT=on > ./instantclient/network/admin/sqlnet.ora \
&& mv ./instantclient /usr/local/ \
&& echo '/usr/local/instantclient' > /etc/ld.so.conf.d/oracle-instantclient.conf \
&& ldconfig \
&& export ORACLE_HOME=instantclient,/usr/local/instantclient \
&& apt install -y sqlite3 libsqlite3-dev libpq-dev \
&& pecl update-channels \
&& docker-php-ext-install mysqli \
&& docker-php-ext-enable mysqli \
&& docker-php-ext-install pdo_pgsql \
&& docker-php-ext-enable pdo_pgsql \
&& docker-php-ext-install pdo_oci \
&& docker-php-ext-enable pdo_oci \
&& docker-php-ext-install pdo_sqlite \
&& docker-php-ext-enable pdo_sqlite \
&& git clone https://github.com/swoole/swoole-src.git \
&& cd ./swoole-src \
&& phpize \
&& ./configure --enable-openssl \
--enable-sockets \
--enable-mysqlnd \
--enable-swoole-curl \
--enable-cares \
--enable-swoole-pgsql \
--with-swoole-oracle=instantclient,/usr/local/instantclient \
--enable-swoole-sqlite \
&& make -j$(cat /proc/cpuinfo | grep processor | wc -l) \
&& make install \
&& docker-php-ext-enable swoole \
&& php -m \
&& php --ri swoole \
&& echo "swoole.enable_library=off" >> /usr/local/etc/php/conf.d/docker-php-ext-swoole.ini && \
{ \
echo '[supervisord]'; \
echo 'user = root'; \
Expand Down
18 changes: 18 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ services:
image: swoole-library
links:
- mysql
- pgsql
- oracle
- redis
- wordpress
- nacos
Expand Down Expand Up @@ -67,6 +69,22 @@ services:
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password

pgsql:
container_name: swoole-library-pgsql
image: postgres:14
environment:
POSTGRES_USER: root
POSTGRES_DB: test
POSTGRES_PASSWORD: root

oracle:
container_name: swoole-library-oracle
image: gvenzl/oracle-xe:slim
environment:
ORACLE_PASSWORD: oracle
ports:
- "1521:1521"

redis:
container_name: swoole-library-redis
image: redis:5.0
Expand Down
1 change: 1 addition & 0 deletions 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
81 changes: 81 additions & 0 deletions 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/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/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/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;
}
}
Loading

0 comments on commit dd9ca3b

Please sign in to comment.