Fix #1705 : MySQL connection might be lost after long operations

This commit is contained in:
Romain Neutron
2014-02-28 12:35:11 +01:00
parent 170c145e15
commit 035576cefc
22 changed files with 128 additions and 68 deletions

View File

@@ -163,6 +163,7 @@ class Manage extends Helper
$stmt = $conn->prepare($sql);
$stmt->execute(array(':email' => $email));
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
$stmt->closeCursor();
$count = count($row);
if (!is_array($row) || $count == 0) {

View File

@@ -1130,6 +1130,7 @@ class ACL implements cache_cacheableInterface
throw new Exception('Error while deleteing some rights');
}
}
$stmt_del->closeCursor();
$this->delete_data_from_cache(self::CACHE_RIGHTS_BAS);
return $this;
@@ -1188,6 +1189,7 @@ class ACL implements cache_cacheableInterface
if (!$this->has_access_to_sbas($sbas_id))
$stmt_ins->execute(array(':sbas_id' => $sbas_id, ':usr_id' => $usr_id));
}
$stmt_ins->closeCursor();
$this->delete_data_from_cache(self::CACHE_RIGHTS_SBAS);
return $this;
@@ -1305,6 +1307,7 @@ class ACL implements cache_cacheableInterface
if (!$stmt_up->execute($params)) {
throw new Exception('Error while updating some rights');
}
$stmt_up->closeCursor();
$this->delete_data_from_cache(self::CACHE_RIGHTS_SBAS);
return $this;

View File

@@ -1420,6 +1420,7 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
':prop' => $prop,
':value' => $value
));
$stmt->closeCursor();
$this->delete_data_from_cache();
} catch (\Exception $e) {

View File

@@ -81,6 +81,7 @@ class collection implements cache_cacheableInterface
$stmt = $connbas->prepare($sql);
$stmt->execute(array(':coll_id' => $this->coll_id));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt->closeCursor();
if ( ! $row)
throw new Exception('Unknown collection ' . $this->coll_id . ' on ' . $this->databox->get_dbname());

View File

@@ -167,6 +167,7 @@ class connection
public static function close_PDO_connection($name)
{
if (isset(self::$_PDO_instance[$name])) {
self::$_PDO_instance[$name]->disconnect();
self::$_PDO_instance[$name] = null;
unset(self::$_PDO_instance[$name]);
}

View File

@@ -15,11 +15,12 @@
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link www.phraseanet.com
*/
abstract class connection_abstract extends PDO
abstract class connection_abstract
{
protected $name;
protected $credentials = array();
protected $multi_db = true;
protected $connection;
public function get_credentials()
{
@@ -42,8 +43,12 @@ abstract class connection_abstract extends PDO
public function ping()
{
if (null === $this->connection) {
$this->initConn();
}
try {
$this->query('SELECT 1');
$this->connection->query('SELECT 1');
} catch (PDOException $e) {
return false;
}
@@ -51,41 +56,16 @@ abstract class connection_abstract extends PDO
return true;
}
/**
*
* @param string $statement
* @param array $driver_options
* @return PDOStatement
*/
public function prepare($statement, $driver_options = array())
{
return parent::prepare($statement, $driver_options);
}
/**
*
* @return boolean
*/
public function beginTransaction()
{
return parent::beginTransaction();
}
/**
*
* @return boolean
*/
public function commit()
{
return parent::commit();
}
/**
*
* @return string
*/
public function server_info()
{
return parent::getAttribute(constant("PDO::ATTR_SERVER_VERSION"));
if (null === $this->connection) {
$this->initConn();
}
return $this->connection->getAttribute(constant("PDO::ATTR_SERVER_VERSION"));
}
}

View File

@@ -17,7 +17,6 @@
*/
interface connection_interface
{
public function ping();
public function get_name();
@@ -28,11 +27,5 @@ interface connection_interface
public function close();
public function prepare($statement, $driver_options = array());
public function beginTransaction();
public function commit();
public function server_info();
}

View File

@@ -36,42 +36,45 @@ class connection_pdo extends connection_abstract implements connection_interface
{
$this->debug = $debug;
$this->name = $name;
if ($dbname)
$dsn = 'mysql:dbname=' . $dbname . ';host=' . $hostname . ';port=' . $port . ';';
else
$dsn = 'mysql:host=' . $hostname . ';port=' . $port . ';';
$this->credentials['hostname'] = $hostname;
$this->credentials['port'] = $port;
$this->credentials['user'] = $user;
$this->credentials['password'] = $passwd;
if ($dbname)
$this->credentials['dbname'] = $dbname;
parent::__construct($dsn, $user, $passwd, $options);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->query("
SET character_set_results = 'utf8', character_set_client = 'utf8',
character_set_connection = 'utf8', character_set_database = 'utf8',
character_set_server = 'utf8'");
$this->initConn();
return $this;
}
protected function initConn()
{
$this->connection = null;
if (isset($this->credentials['dbname']))
$dsn = 'mysql:dbname=' . $this->credentials['dbname'] . ';host=' . $this->credentials['hostname'] . ';port=' . $this->credentials['port'] . ';';
else
$dsn = 'mysql:host=' . $this->credentials['hostname'] . ';port=' . $this->credentials['port'] . ';';
$this->connection = new \PDO($dsn, $this->credentials['user'], $this->credentials['password'], array());
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->connection->exec("
SET character_set_results = 'utf8', character_set_client = 'utf8',
character_set_connection = 'utf8', character_set_database = 'utf8',
character_set_server = 'utf8'");
}
/**
*
* @param type $statement
* @param type $driver_options
* @return PDOStatement
* @return void
*/
public function prepare($statement, $driver_options = array())
public function disconnect()
{
if ($this->debug) {
return new connection_pdoStatementDebugger(parent::prepare($statement, $driver_options));
} else {
return parent::prepare($statement, $driver_options);
}
$this->connection = null;
}
/**
@@ -83,6 +86,48 @@ class connection_pdo extends connection_abstract implements connection_interface
connection::close_PDO_connection($this->name);
}
public function __call($method, $args)
{
if (null === $this->connection) {
$this->initConn();
}
if (!method_exists($this->connection, $method)) {
throw new \BadMethodCallException(sprintf('Method %s does not exist', $method));
}
$tries = 0;
do {
$tries++;
try {
set_error_handler(function ($errno, $errstr) {
if (false !== strpos($errstr, 'Error while sending QUERY packet')) {
throw new \Exception('MySQL server has gone away');
}
throw new \Exception($errstr);
});
if ('prepare' === $method && $this->debug) {
$ret = new connection_pdoStatementDebugger(call_user_func_array(array($this->connection, $method), $args));
} else {
$ret = call_user_func_array(array($this->connection, $method), $args);
}
restore_error_handler();
return $ret;
} catch (\Exception $e) {
restore_error_handler();
$found = (false !== strpos($e->getMessage(), 'MySQL server has gone away')) || (false !== strpos($e->getMessage(), 'errno=32 Broken pipe'));
if ($tries >= 2 || !$found) {
throw $e;
}
$this->initConn();
}
} while (true);
}
/**
*
* @param string $message

View File

@@ -1009,6 +1009,7 @@ class databox extends base
unset($e);
}
}
$stmt->closeCursor();
$user->ACL()->give_access_to_base($base_ids);
foreach ($base_ids as $base_id) {
@@ -1022,8 +1023,6 @@ class databox extends base
);
}
$stmt->closeCursor();
return $this;
}

View File

@@ -413,6 +413,7 @@ class databox_field implements cache_cacheableInterface
$stmt = $connbas->prepare($sql);
$stmt->execute($params);
$stmt->closeCursor();
if ($this->renamed) {
caption_field::rename_all_metadatas($this);

View File

@@ -65,6 +65,7 @@ class patch_370alpha7a implements patchInterface
$stmt = $conn->prepare($sql);
$stmt->execute();
$rs = $stmt->fetchAll();
$stmt->closeCursor();
} catch (\PDOException $e) {
// table not found
if ($e->getCode() == '42S02') {

View File

@@ -72,8 +72,8 @@ class patch_370alpha8a implements patchInterface
if (($stmt = $conn->prepare($sql)) !== FALSE) {
$stmt->execute();
$ttasks = $row = $stmt->fetchAll();
$stmt->closeCursor();
}
$stmt->closeCursor();
$tdom = array(); // key = period
$taskstodel = array();

View File

@@ -74,7 +74,7 @@ class patchthesaurus_204
$sql2 = "UPDATE record SET status=((status | 15) & ~2)";
$stmt = $connbas->prepare($sql);
$stmt = $connbas->prepare($sql2);
$stmt->execute();
$stmt->closeCursor();
}

View File

@@ -1055,6 +1055,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface
':record_id' => $this->record_id
)
);
$stmt->closeCursor();
$this->reindex();
@@ -1181,6 +1182,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface
$sql = 'UPDATE record SET jeton=(jeton | ' . JETON_MAKE_SUBDEF . ') WHERE record_id = :record_id';
$stmt = $connbas->prepare($sql);
$stmt->execute(array(':record_id' => $this->get_record_id()));
$stmt->closeCursor();
return $this;
}
@@ -1197,6 +1199,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface
WHERE record_id= :record_id';
$stmt = $connbas->prepare($sql);
$stmt->execute(array(':record_id' => $this->record_id));
$stmt->closeCursor();
return $this;
}
@@ -1264,6 +1267,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface
':originalname' => null,
':mime' => null,
));
$stmt->closeCursor();
$story_id = $databox->get_connection()->lastInsertId();
@@ -1319,6 +1323,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface
':originalname' => $file->getOriginalName(),
':mime' => $file->getFile()->getMimeType(),
));
$stmt->closeCursor();
$record_id = $databox->get_connection()->lastInsertId();

View File

@@ -761,10 +761,10 @@ class task_period_RecordMover extends task_appboxAbstract
while (($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
$result['rids'][] = $row['record_id'];
}
$stmt->closeCursor();
} else {
$result['err'] = $connbas->last_error();
}
$stmt->closeCursor();
return $result;
}

View File

@@ -70,6 +70,7 @@ class oauthv2_application_test extends \PhraseanetWebTestCaseAuthenticatedAbstra
$t = array(':id' => $app->get_id());
$stmt = $conn->prepare($sql);
$stmt->execute($t);
$stmt->closeCursor();
$sql = '
DELETE FROM api_accounts
WHERE api_account_id = :id
@@ -78,6 +79,7 @@ class oauthv2_application_test extends \PhraseanetWebTestCaseAuthenticatedAbstra
$t = array(':id' => $acc->get_id());
$stmt = $conn->prepare($sql);
$stmt->execute($t);
$stmt->closeCursor();
}
/**
@@ -119,6 +121,7 @@ class oauthv2_application_test extends \PhraseanetWebTestCaseAuthenticatedAbstra
$stmt = $conn->prepare($sql);
$stmt->execute($t);
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
$stmt->closeCursor();
return $result;
}
@@ -131,6 +134,7 @@ class oauthv2_application_test extends \PhraseanetWebTestCaseAuthenticatedAbstra
$stmt = $conn->prepare($sql);
$stmt->execute($t);
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
$stmt->closeCursor();
return new \API_OAuth2_Account(self::$DI['app'], $row["api_account_id"]);
}

View File

@@ -5,7 +5,7 @@ namespace Alchemy\Tests\Phrasea\Authentication\Phrasea;
use Alchemy\Phrasea\Authentication\Phrasea\NativeAuthentication;
use Alchemy\Phrasea\Authentication\Exception\AccountLockedException;
class NativeAuthenticationTest extends \PHPUnit_Framework_TestCase
class NativeAuthenticationTest extends \PhraseanetPHPUnitAbstract
{
/**
* @dataProvider provideReservedUsernames
@@ -16,7 +16,7 @@ class NativeAuthenticationTest extends \PHPUnit_Framework_TestCase
$encoder = $this->getEncoderMock();
$oldEncoder = $this->getOldEncoderMock();
$conn = $this->getMock('connection_interface');
$conn = $this->createConnectionMock();
$request = $this->getRequestMock();
$auth = new NativeAuthentication($encoder, $oldEncoder, $conn);
@@ -180,7 +180,7 @@ class NativeAuthenticationTest extends \PHPUnit_Framework_TestCase
$encoder = $this->getEncoderMock();
$oldEncoder = $this->getOldEncoderMock();
$conn = $this->getMock('connection_interface');
$conn = $this->createConnectionMock();
$statement = $this->getMock('PDOStatement');
$statement
@@ -248,7 +248,7 @@ class NativeAuthenticationTest extends \PHPUnit_Framework_TestCase
private function getConnectionMock($username, $row = null)
{
$conn = $this->getMock('connection_interface');
$conn = $this->createConnectionMock();
$statement = $this->getMock('PDOStatement');

View File

@@ -90,6 +90,7 @@ class PhraseaEngineTest extends SearchEngineAbstractTest
break;
}
$stmt->closeCursor();
$date = new \DateTime('-1 months');

View File

@@ -107,6 +107,8 @@ abstract class PhraseanetPHPUnitAbstract extends WebTestCase
parent::setUp();
connection::close_connections();
\PHPUnit_Framework_Error_Warning::$enabled = true;
\PHPUnit_Framework_Error_Notice::$enabled = true;
@@ -989,4 +991,9 @@ abstract class PhraseanetPHPUnitAbstract extends WebTestCase
->disableOriginalConstructor()
->getMock();
}
public function createConnectionMock()
{
return $this->getMock('connection_interface', array('close', 'get_credentials', 'server_info', 'prepare', 'beginTransaction', 'commit', 'ping', 'get_name', 'is_multi_db'));
}
}

View File

@@ -136,10 +136,12 @@ abstract class PhraseanetWebTestCaseAuthenticatedAbstract extends PhraseanetPHPU
->get_connection()
->prepare('DROP DATABASE IF EXISTS `unit_test_db`');
$stmt->execute();
$stmt->closeCursor();
$stmt = self::$DI['app']['phraseanet.appbox']
->get_connection()
->prepare('DELETE FROM sbas WHERE dbname = "unit_test_db"');
$stmt->execute();
$stmt->closeCursor();
}
protected function createDatabase()

View File

@@ -47,6 +47,7 @@ class Session_LoggerTest extends PhraseanetPHPUnitAbstract
$stmt->execute($params);
$this->assertEquals(1, $stmt->rowCount());
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt->closeCursor();
$this->assertEquals($this->object->get_id(), $row['id']);
$log_id = $this->object->get_id();
$ses_id = self::$DI['app']['session']->get('session_id');
@@ -67,5 +68,6 @@ class Session_LoggerTest extends PhraseanetPHPUnitAbstract
$this->assertEquals(1, $stmt->rowCount());
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$this->assertEquals($log_id, $row['id']);
$stmt->closeCursor();
}
}

View File

@@ -0,0 +1,13 @@
<?php
class connectionTest extends \PhraseanetPHPUnitAbstract
{
public function testMysqlTimeoutIsHandled()
{
$conn = connection::getPDOConnection(self::$DI['app']);
$conn->exec('SET @@local.wait_timeout= 1');
usleep(1200000);
$conn->exec('SHOW DATABASES');
connection::close_connections();
}
}