+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2015 |
+ | Copyright CiviCRM LLC (c) 2004-2016 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
/**
*
* @package CRM
- * @copyright CiviCRM LLC (c) 2004-2015
+ * @copyright CiviCRM LLC (c) 2004-2016
* $Id$
*
*/
class CRM_Core_Lock implements \Civi\Core\Lock\LockInterface {
+ /**
+ * This variable (despite it's name) roughly translates to 'lock that we actually care about'.
+ *
+ * Prior to version 5.7.5 mysql only supports a single named lock. This variable is
+ * part of the skullduggery involved in 'say it's no so Frank'.
+ *
+ * See further comments on the aquire function.
+ *
+ * @var bool
+ */
static $jobLog = FALSE;
// lets have a 3 second timeout for now
protected $_name;
+ protected $_id;
+
/**
* Use MySQL's GET_LOCK(). Locks are shared across all Civi instances
* on the same MySQL server.
* @deprecated
*/
public static function createCivimailLock($name) {
- $serverWideLock = \CRM_Core_BAO_Setting::getItem(
- \CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
- 'civimail_server_wide_lock'
- );
+ $serverWideLock = \Civi::settings()->get('civimail_server_wide_lock');
return new static($name, NULL, $serverWideLock);
}
else {
$this->_name = $database . '.' . $domainID . '.' . $name;
}
+ // MySQL 5.7 doesn't like long lock names so creating a lock id
+ $this->_id = sha1($this->_name);
if (defined('CIVICRM_LOCK_DEBUG')) {
- CRM_Core_Error::debug_log_message('trying to construct lock for ' . $this->_name);
+ CRM_Core_Error::debug_log_message('trying to construct lock for ' . $this->_name . '(' . $this->_id . ')');
}
$this->_timeout = $timeout !== NULL ? $timeout : self::TIMEOUT;
}
}
/**
+ * Acquire lock.
+ *
+ * The advantage of mysql locks is that they can be used across processes. However, only one
+ * can be used at once within a process. An attempt to use a second one within a process
+ * prior to mysql 5.7.5 results in the first being released.
+ *
+ * The process here is
+ * 1) first attempt to grab a lock for a mailing job - self::jobLog will be populated with the
+ * lock id & a mysql lock will be created for the ID.
+ *
+ * If a second function in the same process attempts to grab the lock it will enter the hackyHandleBrokenCode routine
+ * which says 'I won't break a mailing lock for you but if you are not a civimail send process I'll let you
+ * pretend you have a lock already and you can go ahead with whatever you were doing under the delusion you
+ * have a lock.
+ *
+ * @todo bypass hackyHandleBrokenCode for mysql version 5.7.5+
+ *
+ * If a second function in a separate process attempts to grab the lock already in use it should be rejected,
+ * but it appears it IS allowed to grab a different lock & unlike in the same process the first lock won't be released.
+ *
+ * All this means CiviMail locks are first class citizens & any other process gets a 'best effort lock'.
+ *
+ * @todo document naming convention for CiviMail locks as this is key to ensuring they work properly.
+ *
+ * @param int $timeout
+ *
* @return bool
+ * @throws \CRM_Core_Exception
*/
public function acquire($timeout = NULL) {
if (!$this->_hasLock) {
$query = "SELECT GET_LOCK( %1, %2 )";
$params = array(
- 1 => array($this->_name, 'String'),
+ 1 => array($this->_id, 'String'),
2 => array($timeout ? $timeout : $this->_timeout, 'Integer'),
);
$res = CRM_Core_DAO::singleValueQuery($query, $params);
if ($res) {
if (defined('CIVICRM_LOCK_DEBUG')) {
- CRM_Core_Error::debug_log_message('acquire lock for ' . $this->_name);
+ CRM_Core_Error::debug_log_message('acquire lock for ' . $this->_name . '(' . $this->_id . ')');
}
$this->_hasLock = TRUE;
if (stristr($this->_name, 'data.mailing.job.')) {
- self::$jobLog = $this->_name;
+ self::$jobLog = $this->_id;
}
}
else {
if (defined('CIVICRM_LOCK_DEBUG')) {
- CRM_Core_Error::debug_log_message('failed to acquire lock for ' . $this->_name);
+ CRM_Core_Error::debug_log_message('failed to acquire lock for ' . $this->_name . '(' . $this->_id . ')');
}
}
}
public function release() {
if ($this->_hasLock) {
if (defined('CIVICRM_LOCK_DEBUG')) {
- CRM_Core_Error::debug_log_message('release lock for ' . $this->_name);
+ CRM_Core_Error::debug_log_message('release lock for ' . $this->_name . '(' . $this->_id . ')');
}
$this->_hasLock = FALSE;
- if (self::$jobLog == $this->_name) {
+ if (self::$jobLog == $this->_id) {
self::$jobLog = FALSE;
}
$query = "SELECT RELEASE_LOCK( %1 )";
- $params = array(1 => array($this->_name, 'String'));
+ $params = array(1 => array($this->_id, 'String'));
return CRM_Core_DAO::singleValueQuery($query, $params);
}
}
*/
public function isFree() {
$query = "SELECT IS_FREE_LOCK( %1 )";
- $params = array(1 => array($this->_name, 'String'));
+ $params = array(1 => array($this->_id, 'String'));
return CRM_Core_DAO::singleValueQuery($query, $params);
}
*/
public function hackyHandleBrokenCode($jobLog) {
if (stristr($this->_name, 'job')) {
- CRM_Core_Error::debug_log_message('lock acquisition for ' . $this->_name . ' attempted when ' . $jobLog . ' is not released');
- throw new CRM_Core_Exception('lock acquisition for ' . $this->_name . ' attempted when ' . $jobLog . ' is not released');
+ CRM_Core_Error::debug_log_message('lock acquisition for ' . $this->_name . '(' . $this->_id . ')' . ' attempted when ' . $jobLog . ' is not released');
+ throw new CRM_Core_Exception('lock acquisition for ' . $this->_name . '(' . $this->_id . ')' . ' attempted when ' . $jobLog . ' is not released');
}
if (defined('CIVICRM_LOCK_DEBUG')) {
- CRM_Core_Error::debug_log_message('(CRM-12856) faking lock for ' . $this->_name);
+ CRM_Core_Error::debug_log_message('(CRM-12856) faking lock for ' . $this->_name . '(' . $this->_id . ')');
}
$this->_hasLock = TRUE;
return TRUE;