Merge pull request #1006 from romainneutron/fix-connection-master

[Ready][3.9] Add reconnectable connection
This commit is contained in:
Nicolas Le Goff
2014-03-04 11:19:44 +01:00
26 changed files with 231 additions and 27 deletions

View File

@@ -14,6 +14,7 @@ namespace Alchemy\Phrasea;
use Alchemy\Phrasea\Command\CommandInterface;
use Alchemy\Phrasea\Core\CLIProvider\TranslationExtractorServiceProvider;
use Alchemy\Phrasea\Core\CLIProvider\WebsocketServerServiceProvider;
use Alchemy\Phrasea\Core\PhraseaCLIExceptionHandler;
use Alchemy\Phrasea\Exception\RuntimeException;
use Symfony\Component\Console;
use Alchemy\Phrasea\Core\CLIProvider\CLIDriversServiceProvider;
@@ -23,6 +24,7 @@ use Alchemy\Phrasea\Core\CLIProvider\LessBuilderServiceProvider;
use Alchemy\Phrasea\Core\CLIProvider\PluginServiceProvider;
use Alchemy\Phrasea\Core\CLIProvider\SignalHandlerServiceProvider;
use Alchemy\Phrasea\Core\CLIProvider\TaskManagerServiceProvider;
use Symfony\Component\Debug\ErrorHandler;
/**
* Phraseanet Command Line Application
@@ -66,6 +68,10 @@ class CLI extends Application
$this->register(new DoctrineMigrationServiceProvider());
$this->bindRoutes();
error_reporting(-1);
ErrorHandler::register();
PhraseaCLIExceptionHandler::register();
}
/**

View File

@@ -12,7 +12,7 @@
namespace Alchemy\Phrasea\Command\Setup;
use Alchemy\Phrasea\Command\Command;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

View File

@@ -955,12 +955,15 @@ class Collection implements ControllerProviderInterface
$success = false;
$collection = \collection::get_from_base_id($app, $bas_id);
$prefs = $request->request->get('str');
try {
$domdoc = new \DOMDocument();
if ($domdoc->loadXML($request->request->get('str'))) {
$collection->set_prefs($domdoc);
$success = true;
if ('' !== trim($prefs)) {
$domdoc = new \DOMDocument();
if (true === $domdoc->loadXML($prefs)) {
$collection->set_prefs($domdoc);
$success = true;
}
}
} catch (\Exception $e) {

View File

@@ -11,7 +11,7 @@
namespace Alchemy\Phrasea\Controller\Thesaurus;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
use Silex\Application;
use Silex\ControllerProviderInterface;
use Symfony\Component\HttpFoundation\Request;

View File

@@ -13,8 +13,9 @@ namespace Alchemy\Phrasea\Core\Connection;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\DriverManager;
use Psr\Log\LoggerInterface;
class ConnectionProvider
{
@@ -24,9 +25,11 @@ class ConnectionProvider
*/
private $connections = [];
private $eventManager;
private $logger;
public function __construct(Configuration $config, EventManager $eventManager)
public function __construct(Configuration $config, EventManager $eventManager, LoggerInterface $logger)
{
$this->logger = $logger;
$this->config = $config;
$this->eventManager = $eventManager;
}
@@ -58,6 +61,6 @@ class ConnectionProvider
return $this->connections[$key];
}
return $this->connections[$key] = DriverManager::getConnection($params, $this->config, $this->eventManager);;
return $this->connections[$key] = new ReconnectableConnection(DriverManager::getConnection($params, $this->config, $this->eventManager), $this->logger);
}
}

View File

@@ -0,0 +1,151 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core\Connection;
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
use Psr\Log\LoggerInterface;
class ReconnectableConnection implements ConnectionInterface
{
const MYSQL_CONNECTION_TIMED_WAIT_CODE = 2006;
/** @var Connection */
private $connection;
private $logger;
public function __construct(ConnectionInterface $connection, LoggerInterface $logger)
{
$this->connection = $connection;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
function prepare($prepareString)
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function query()
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function quote($input, $type=\PDO::PARAM_STR)
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function exec($statement)
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function lastInsertId($name = null)
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function beginTransaction()
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function commit()
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function rollBack()
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function errorCode()
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
function errorInfo()
{
return $this->tryMethod(__FUNCTION__, func_get_args());
}
/**
* {@inheritdoc}
*/
public function __call($method, $args)
{
return $this->tryMethod($method, $args);
}
private function tryMethod($method, $args)
{
try {
return call_user_func_array([$this->connection, $method], $args);
} catch (\Exception $exception) {
$e = $exception;
while ($e->getPrevious() && !$e instanceof \PDOException) {
$e = $e->getPrevious();
}
if ($e instanceof \PDOException && $e->errorInfo[1] == self::MYSQL_CONNECTION_TIMED_WAIT_CODE) {
$this->connection->close();
$this->connection->connect();
$this->logger->notice('Connection to MySQL lost, reconnect okay.');
return call_user_func_array([$this->connection, $method], $args);
}
if (
(false !== strpos($exception->getMessage(), 'MySQL server has gone away'))
|| (false !== strpos($exception->getMessage(), 'Error while sending QUERY packet'))
|| (false !== strpos($exception->getMessage(), 'errno=32 Broken pipe'))
) {
$this->connection->close();
$this->connection->connect();
$this->logger->notice('Connection to MySQL lost, reconnect okay.');
return call_user_func_array([$this->connection, $method], $args);
}
$this->logger->critical('Connection to MySQL lost, unable to reconnect.', ['exception' => $exception]);
throw $e;
}
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Core;
use Symfony\Component\Debug\ExceptionHandler as SymfonyExceptionHandler;
class PhraseaCLIExceptionHandler extends SymfonyExceptionHandler
{
public function handle(\Exception $exception)
{
throw $exception;
}
}

View File

@@ -150,7 +150,7 @@ class ORMServiceProvider implements ServiceProviderInterface
});
$app['dbal.provider'] = $app->share(function (Application $app) {
return new ConnectionProvider($app['EM.config'], $app['EM.events-manager']);
return new ConnectionProvider($app['EM.config'], $app['EM.events-manager'], isset($app['task-manager.logger']) ? $app['task-manager.logger'] : $app['monolog']);
});
$app['EM'] = $app->share(function (Application $app) {

View File

@@ -14,7 +14,7 @@ namespace Alchemy\Phrasea\Model\Manager;
use Doctrine\Common\Persistence\ObjectManager;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\UserSetting;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\ORM\UnitOfWork AS UOW;
class UserManager

View File

@@ -12,7 +12,7 @@
namespace Alchemy\Phrasea\Setup;
use Alchemy\Phrasea\Application;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\Tools\SchemaTool;
use Alchemy\Phrasea\Model\Entities\User;

View File

@@ -13,7 +13,7 @@ use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Version as PhraseaVersion;
use vierbergenlars\SemVer\version;
use Alchemy\Phrasea\Model\Entities\User;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
abstract class base implements cache_cacheableInterface
{

View File

@@ -12,7 +12,7 @@
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Model\Entities\User;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
class collection implements cache_cacheableInterface
{

View File

@@ -12,7 +12,7 @@
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Translation\TranslatorInterface;

View File

@@ -13,7 +13,7 @@ use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Vocabulary;
use Alchemy\Phrasea\Vocabulary\ControlProvider\ControlProviderInterface;
use Alchemy\Phrasea\Metadata\Tag\Nosource;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
use PHPExiftool\Driver\TagInterface;
use PHPExiftool\Driver\TagFactory;
use PHPExiftool\Exception\TagUnknown;

View File

@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
class patchthesaurus_100 implements patchthesaurus_interface
{

View File

@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
class patchthesaurus_200 implements patchthesaurus_interface
{

View File

@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
class patchthesaurus_201 implements patchthesaurus_interface
{

View File

@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
class patchthesaurus_202 implements patchthesaurus_interface
{

View File

@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
class patchthesaurus_203 implements patchthesaurus_interface
{

View File

@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
class patchthesaurus_204 implements patchthesaurus_interface
{

View File

@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\Connection;
interface patchthesaurus_interface
{

View File

@@ -84,7 +84,7 @@ class InstallTest extends \PhraseanetTestCase
self::$DI['cli']['phraseanet.installer']->expects($this->once())
->method('install')
->with($email, $password, $this->isInstanceOf('Doctrine\DBAL\Connection'), $serverName, $dataPath, $this->isInstanceOf('Doctrine\DBAL\Connection'), $template, $this->anything());
->with($email, $password, $this->isInstanceOf('Doctrine\DBAL\Driver\Connection'), $serverName, $dataPath, $this->isInstanceOf('Doctrine\DBAL\Driver\Connection'), $template, $this->anything());
$command = new Install('system:check');
$command->setHelperSet($helperSet);

View File

@@ -0,0 +1,17 @@
<?php
namespace Alchemy\Tests\Phrasea\Core\Connection;
use Alchemy\Phrasea\Core\Connection\ConnectionProvider;
class ConnectionProviderTest extends \PhraseanetTestCase
{
public function testMysqlTimeoutIsHandled()
{
$provider = new ConnectionProvider(self::$DI['app']['EM.config'], self::$DI['app']['EM.events-manager'], $this->createLoggerMock());
$conn = $provider->get(self::$DI['app']['conf']->get(['main', 'database']));
$conn->exec('SET @@local.wait_timeout= 1');
usleep(1200000);
$conn->exec('SHOW DATABASES');
}
}

View File

@@ -112,7 +112,7 @@ class collectionTest extends \PhraseanetAuthenticatedTestCase
public function testGet_connection()
{
$this->assertInstanceOf('Doctrine\DBAL\Connection', self::$object->get_connection());
$this->assertInstanceOf('Doctrine\DBAL\Driver\Connection', self::$object->get_connection());
}
/**

View File

@@ -70,8 +70,8 @@ class databox_fieldTest extends \PhraseanetTestCase
public function testGet_connection()
{
$this->assertInstanceOf('Doctrine\DBAL\Connection', $this->object_mono->get_connection());
$this->assertInstanceOf('Doctrine\DBAL\Connection', $this->object_multi->get_connection());
$this->assertInstanceOf('Doctrine\DBAL\Driver\Connection', $this->object_mono->get_connection());
$this->assertInstanceOf('Doctrine\DBAL\Driver\Connection', $this->object_multi->get_connection());
}
public function testGet_databox()

View File

@@ -14,6 +14,8 @@ use Symfony\Component\Debug\ErrorHandler;
require_once __DIR__ . "/../lib/autoload.php";
error_reporting(-1);
ErrorHandler::register();
$environment = Application::ENV_DEV;