Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * MySQL / MariaDB locking factory.
  19   *
  20   * @package    core
  21   * @category   lock
  22   * @copyright  Brendan Heywood <brendan@catalyst-au.net>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace core\lock;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  /**
  31   * MySQL / MariaDB locking factory.
  32   *
  33   * @package   core
  34   * @category  lock
  35   * @copyright Brendan Heywood <brendan@catalyst-au.net>
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class mysql_lock_factory implements lock_factory {
  39  
  40      /** @var string $dbprefix - used as a namespace for these types of locks */
  41      protected $dbprefix = '';
  42  
  43      /** @var \moodle_database $db Hold a reference to the global $DB */
  44      protected $db;
  45  
  46      /** @var array $openlocks - List of held locks - used by auto-release */
  47      protected $openlocks = [];
  48  
  49      /**
  50       * Return a unique prefix based on the database name and prefix.
  51       * @param string $type - Used to prefix lock keys.
  52       * @return string.
  53       */
  54      protected function get_unique_db_prefix($type) {
  55          global $CFG;
  56          $prefix = $CFG->dbname . ':';
  57          if (isset($CFG->prefix)) {
  58              $prefix .= $CFG->prefix;
  59          }
  60          $prefix .= '_' . $type . '_';
  61          return $prefix;
  62      }
  63  
  64      /**
  65       * Lock constructor.
  66       * @param string $type - Used to prefix lock keys.
  67       */
  68      public function __construct($type) {
  69          global $DB;
  70  
  71          $this->dbprefix = $this->get_unique_db_prefix($type);
  72          // Save a reference to the global $DB so it will not be released while we still have open locks.
  73          $this->db = $DB;
  74  
  75          \core_shutdown_manager::register_function([$this, 'auto_release']);
  76      }
  77  
  78      /**
  79       * Is available.
  80       * @return boolean - True if this lock type is available in this environment.
  81       */
  82      public function is_available() {
  83          return $this->db->get_dbfamily() === 'mysql';
  84      }
  85  
  86      /**
  87       * Return information about the blocking behaviour of the lock type on this platform.
  88       * @return boolean - Defer to the DB driver.
  89       */
  90      public function supports_timeout() {
  91          return true;
  92      }
  93  
  94      /**
  95       * Will this lock type will be automatically released when a process ends.
  96       *
  97       * @return boolean - Via shutdown handler.
  98       */
  99      public function supports_auto_release() {
 100          return true;
 101      }
 102  
 103      /**
 104       * Multiple locks for the same resource can NOT be held by a single process.
 105       *
 106       * Hard coded to false and workaround inconsistent support in different
 107       * versions of MySQL / MariaDB.
 108       *
 109       * @deprecated since Moodle 3.10.
 110       * @return boolean - false
 111       */
 112      public function supports_recursion() {
 113          debugging('The function supports_recursion() is deprecated, please do not use it anymore.',
 114              DEBUG_DEVELOPER);
 115          return false;
 116      }
 117  
 118      /**
 119       * Create and get a lock
 120       * @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
 121       * @param int $timeout - The number of seconds to wait for a lock before giving up.
 122       * @param int $maxlifetime - Unused by this lock type.
 123       * @return boolean - true if a lock was obtained.
 124       */
 125      public function get_lock($resource, $timeout, $maxlifetime = 86400) {
 126  
 127          // We sha1 to avoid long key names hitting the mysql str limit.
 128          $resourcekey = sha1($this->dbprefix . $resource);
 129  
 130          // Even though some versions of MySQL and MariaDB can support stacked locks
 131          // just never stack them and always fail fast.
 132          if (isset($this->openlocks[$resourcekey])) {
 133              return false;
 134          }
 135  
 136          $params = [
 137              'resourcekey' => $resourcekey,
 138              'timeout' => $timeout
 139          ];
 140  
 141          $result = $this->db->get_record_sql('SELECT GET_LOCK(:resourcekey, :timeout) AS locked', $params);
 142          $locked = $result->locked == 1;
 143  
 144          if ($locked) {
 145              $this->openlocks[$resourcekey] = 1;
 146              return new lock($resourcekey, $this);
 147          }
 148          return false;
 149      }
 150  
 151      /**
 152       * Release a lock that was previously obtained with @lock.
 153       * @param lock $lock - a lock obtained from this factory.
 154       * @return boolean - true if the lock is no longer held (including if it was never held).
 155       */
 156      public function release_lock(lock $lock) {
 157  
 158          $params = [
 159              'resourcekey' => $lock->get_key()
 160          ];
 161          $result = $this->db->get_record_sql('SELECT RELEASE_LOCK(:resourcekey) AS unlocked', $params);
 162          $result = $result->unlocked == 1;
 163          if ($result) {
 164              unset($this->openlocks[$lock->get_key()]);
 165          }
 166          return $result;
 167      }
 168  
 169      /**
 170       * Extend a lock that was previously obtained with @lock.
 171       *
 172       * @deprecated since Moodle 3.10.
 173       * @param lock $lock - a lock obtained from this factory.
 174       * @param int $maxlifetime - the new lifetime for the lock (in seconds).
 175       * @return boolean - true if the lock was extended.
 176       */
 177      public function extend_lock(lock $lock, $maxlifetime = 86400) {
 178          debugging('The function extend_lock() is deprecated, please do not use it anymore.',
 179              DEBUG_DEVELOPER);
 180          // Not supported by this factory.
 181          return false;
 182      }
 183  
 184      /**
 185       * Auto release any open locks on shutdown.
 186       * This is required, because we may be using persistent DB connections.
 187       */
 188      public function auto_release() {
 189          // Called from the shutdown handler. Must release all open locks.
 190          foreach ($this->openlocks as $key => $unused) {
 191              $lock = new lock($key, $this);
 192              $lock->release();
 193          }
 194      }
 195  
 196  }