Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   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  namespace core\lock;
  18  
  19  use coding_exception;
  20  
  21  /**
  22   * MySQL / MariaDB locking factory.
  23   *
  24   * @package   core
  25   * @category  lock
  26   * @copyright Brendan Heywood <brendan@catalyst-au.net>
  27   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   */
  29  class mysql_lock_factory implements lock_factory {
  30  
  31      /** @var string $dbprefix - used as a namespace for these types of locks */
  32      protected $dbprefix = '';
  33  
  34      /** @var \moodle_database $db Hold a reference to the global $DB */
  35      protected $db;
  36  
  37      /** @var array $openlocks - List of held locks - used by auto-release */
  38      protected $openlocks = [];
  39  
  40      /**
  41       * Return a unique prefix based on the database name and prefix.
  42       * @param string $type - Used to prefix lock keys.
  43       * @return string
  44       */
  45      protected function get_unique_db_prefix($type) {
  46          global $CFG;
  47          $prefix = $CFG->dbname . ':';
  48          if (isset($CFG->prefix)) {
  49              $prefix .= $CFG->prefix;
  50          }
  51          $prefix .= '_' . $type . '_';
  52          return $prefix;
  53      }
  54  
  55      /**
  56       * Lock constructor.
  57       * @param string $type - Used to prefix lock keys.
  58       */
  59      public function __construct($type) {
  60          global $DB;
  61  
  62          $this->dbprefix = $this->get_unique_db_prefix($type);
  63          // Save a reference to the global $DB so it will not be released while we still have open locks.
  64          $this->db = $DB;
  65  
  66          \core_shutdown_manager::register_function([$this, 'auto_release']);
  67      }
  68  
  69      /**
  70       * Is available.
  71       * @return boolean - True if this lock type is available in this environment.
  72       */
  73      public function is_available() {
  74          return $this->db->get_dbfamily() === 'mysql';
  75      }
  76  
  77      /**
  78       * Return information about the blocking behaviour of the lock type on this platform.
  79       * @return boolean - Defer to the DB driver.
  80       */
  81      public function supports_timeout() {
  82          return true;
  83      }
  84  
  85      /**
  86       * Will this lock type will be automatically released when a process ends.
  87       *
  88       * @return boolean - Via shutdown handler.
  89       */
  90      public function supports_auto_release() {
  91          return true;
  92      }
  93  
  94      /**
  95       * @deprecated since Moodle 3.10.
  96       */
  97      public function supports_recursion() {
  98          throw new coding_exception('The function supports_recursion() has been removed, please do not use it anymore.');
  99      }
 100  
 101      /**
 102       * Create and get a lock
 103       * @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
 104       * @param int $timeout - The number of seconds to wait for a lock before giving up.
 105       * @param int $maxlifetime - Unused by this lock type.
 106       * @return boolean - true if a lock was obtained.
 107       */
 108      public function get_lock($resource, $timeout, $maxlifetime = 86400) {
 109  
 110          // We sha1 to avoid long key names hitting the mysql str limit.
 111          $resourcekey = sha1($this->dbprefix . $resource);
 112  
 113          // Even though some versions of MySQL and MariaDB can support stacked locks
 114          // just never stack them and always fail fast.
 115          if (isset($this->openlocks[$resourcekey])) {
 116              return false;
 117          }
 118  
 119          $params = [
 120              'resourcekey' => $resourcekey,
 121              'timeout' => $timeout
 122          ];
 123  
 124          $result = $this->db->get_record_sql('SELECT GET_LOCK(:resourcekey, :timeout) AS locked', $params);
 125          $locked = $result->locked == 1;
 126  
 127          if ($locked) {
 128              $this->openlocks[$resourcekey] = 1;
 129              return new lock($resourcekey, $this);
 130          }
 131          return false;
 132      }
 133  
 134      /**
 135       * Release a lock that was previously obtained with @lock.
 136       * @param lock $lock - a lock obtained from this factory.
 137       * @return boolean - true if the lock is no longer held (including if it was never held).
 138       */
 139      public function release_lock(lock $lock) {
 140  
 141          $params = [
 142              'resourcekey' => $lock->get_key()
 143          ];
 144          $result = $this->db->get_record_sql('SELECT RELEASE_LOCK(:resourcekey) AS unlocked', $params);
 145          $result = $result->unlocked == 1;
 146          if ($result) {
 147              unset($this->openlocks[$lock->get_key()]);
 148          }
 149          return $result;
 150      }
 151  
 152      /**
 153       * @deprecated since Moodle 3.10.
 154       */
 155      public function extend_lock() {
 156          throw new coding_exception('The function extend_lock() has been removed, please do not use it anymore.');
 157      }
 158  
 159      /**
 160       * Auto release any open locks on shutdown.
 161       * This is required, because we may be using persistent DB connections.
 162       */
 163      public function auto_release() {
 164          // Called from the shutdown handler. Must release all open locks.
 165          foreach ($this->openlocks as $key => $unused) {
 166              $lock = new lock($key, $this);
 167              $lock->release();
 168          }
 169      }
 170  
 171  }