mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-24 02:58:43 +02:00
Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts: # core/cmdbsource.class.inc.php # core/coreexception.class.inc.php # core/log.class.inc.php
This commit is contained in:
@@ -585,6 +585,11 @@ class CMDBSource
|
||||
*/
|
||||
private static function DBQuery($sSql)
|
||||
{
|
||||
$sShortSQL = str_replace("\n", " ", substr($sSql, 0, 120));
|
||||
if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) {
|
||||
IssueLog::Trace("$sShortSQL", 'cmdbsource');
|
||||
}
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
@@ -668,10 +673,15 @@ class CMDBSource
|
||||
*/
|
||||
private static function StartTransaction()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
$bHasExistingTransactions = self::IsInsideTransaction();
|
||||
if (!$bHasExistingTransactions)
|
||||
{
|
||||
IssueLog::Trace("START TRANSACTION $sCaller", 'cmdbsource');
|
||||
self::DBQuery('START TRANSACTION');
|
||||
} else {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", 'cmdbsource');
|
||||
}
|
||||
|
||||
self::AddTransactionLevel();
|
||||
@@ -689,9 +699,12 @@ class CMDBSource
|
||||
*/
|
||||
private static function Commit()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction COMMIT $sCaller", 'cmdbsource');
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
|
||||
@@ -699,8 +712,10 @@ class CMDBSource
|
||||
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", 'cmdbsource');
|
||||
return;
|
||||
}
|
||||
IssueLog::Trace("COMMIT $sCaller", 'cmdbsource');
|
||||
self::DBQuery('COMMIT');
|
||||
}
|
||||
|
||||
@@ -719,17 +734,22 @@ class CMDBSource
|
||||
*/
|
||||
private static function Rollback()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction ROLLBACK $sCaller", 'cmdbsource');
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
self::RemoveLastTransactionLevel();
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", 'cmdbsource');
|
||||
return;
|
||||
}
|
||||
|
||||
IssueLog::Trace("ROLLBACK $sCaller", 'cmdbsource');
|
||||
self::DBQuery('ROLLBACK');
|
||||
}
|
||||
|
||||
@@ -779,6 +799,18 @@ class CMDBSource
|
||||
self::$m_iTransactionLevel = 0;
|
||||
}
|
||||
|
||||
public static function IsDeadlockException(Exception $e)
|
||||
{
|
||||
while ($e instanceof Exception) {
|
||||
if (($e instanceof MySQLException) && ($e->getCode() == 1213)) {
|
||||
return true;
|
||||
}
|
||||
$e = $e->getPrevious();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static function GetInsertId()
|
||||
{
|
||||
$iRes = self::$m_oMysqli->insert_id;
|
||||
|
||||
@@ -2778,50 +2778,72 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
$iTransactionRetry = 1;
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
try
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
|
||||
// First query built upon on the root class, because the ID must be created first
|
||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||
|
||||
// Then do the leaf class, if different from the root class
|
||||
if ($sClass != $sRootClass)
|
||||
{
|
||||
$this->DBInsertSingleTable($sClass);
|
||||
}
|
||||
|
||||
// Then do the other classes
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
|
||||
{
|
||||
if ($sParentClass == $sRootClass)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
while ($iTransactionRetry > 0) {
|
||||
try {
|
||||
$iTransactionRetry--;
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
|
||||
// First query built upon on the root class, because the ID must be created first
|
||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||
|
||||
// Then do the leaf class, if different from the root class
|
||||
if ($sClass != $sRootClass) {
|
||||
$this->DBInsertSingleTable($sClass);
|
||||
}
|
||||
|
||||
// Then do the other classes
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) {
|
||||
if ($sParentClass == $sRootClass) {
|
||||
continue;
|
||||
}
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Insert TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Error("Insert Deadlock TRANSACTION prevention failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->m_bIsInDB = true;
|
||||
@@ -3185,9 +3207,11 @@ abstract class DBObject implements iDisplay
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iIsTransactionRetryCount;
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
@@ -3275,18 +3299,18 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if ($e->getCode() == 1213)
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction
|
||||
IssueLog::Error($e->getMessage());
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Update TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -3300,10 +3324,11 @@ abstract class DBObject implements iDisplay
|
||||
'id' => $this->GetKey(),
|
||||
'class' => get_class($this),
|
||||
'issues' => $aErrors
|
||||
));
|
||||
), $e);
|
||||
}
|
||||
catch (CoreCannotSaveObjectException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -3312,6 +3337,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -3514,9 +3540,11 @@ abstract class DBObject implements iDisplay
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iIsTransactionRetryCount;
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
@@ -3539,18 +3567,18 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if ($e->getCode() == 1213)
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Delete TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
|
||||
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -503,6 +503,7 @@ class FileLog
|
||||
protected function Write($sText, $sLevel = '', $sChannel = '', $aContext = array())
|
||||
{
|
||||
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7).' | ');
|
||||
$sTextPrefix .= str_pad(UserRights::GetUserId(), 5)." | ";
|
||||
$sTextSuffix = empty($sChannel) ? '' : " | $sChannel";
|
||||
$sText = "{$sTextPrefix}{$sText}{$sTextSuffix}";
|
||||
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
|
||||
|
||||
@@ -242,6 +242,8 @@ class iTopMutex
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @since 2.7.5 3.0.0 N°3968 specify `wait_timeout` for the mutex dedicated connection
|
||||
*/
|
||||
public function InitMySQLSession()
|
||||
{
|
||||
@@ -254,10 +256,36 @@ class iTopMutex
|
||||
|
||||
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
|
||||
|
||||
if (!$this->hDBLink)
|
||||
{
|
||||
if (!$this->hDBLink) {
|
||||
throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
||||
}
|
||||
|
||||
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,
|
||||
// since the lock will be released if/when the connection times out.
|
||||
// Source https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html :
|
||||
// > A lock obtained with GET_LOCK() is released explicitly by executing RELEASE_LOCK() or implicitly when your session terminates
|
||||
//
|
||||
// BEWARE: If you want to check the value of this variable, when run from an interactive console `SHOW VARIABLES LIKE 'wait_timeout'`
|
||||
// will actually returns the value of the variable `interactive_timeout` which may be quite different.
|
||||
$sSql = "SHOW VARIABLES LIKE 'wait_timeout'";
|
||||
$result = mysqli_query($this->hDBLink, $sSql);
|
||||
if (!$result) {
|
||||
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
|
||||
}
|
||||
if ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH)) {
|
||||
$iTimeout = (int)$aRow[1];
|
||||
} else {
|
||||
mysqli_free_result($result);
|
||||
throw new Exception("No result for query '".$sSql."'");
|
||||
}
|
||||
mysqli_free_result($result);
|
||||
|
||||
if ($iTimeout < 86400) {
|
||||
$result = mysqli_query($this->hDBLink, 'SET SESSION wait_timeout=86400');
|
||||
if ($result === false) {
|
||||
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user