See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 401] [Versions 401 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 /** 18 * Postgres advisory locking factory. 19 * 20 * @package core 21 * @category lock 22 * @copyright Damyon Wiese 2013 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 * Postgres advisory locking factory. 32 * 33 * Postgres locking implementation using advisory locks. Some important points. Postgres has 34 * 2 different forms of lock functions, some accepting a single int, and some accepting 2 ints. This implementation 35 * uses the 2 int version so that it uses a separate namespace from the session locking. The second note, 36 * is because postgres uses integer keys for locks, we first need to map strings to a unique integer. This is done 37 * using a prefix of a sha1 hash converted to an integer. 38 * 39 * @package core 40 * @category lock 41 * @copyright Damyon Wiese 2013 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 */ 44 class postgres_lock_factory implements lock_factory { 45 46 /** @var int $dblockid - used as a namespace for these types of locks (separate from session locks) */ 47 protected $dblockid = -1; 48 49 /** @var array $lockidcache - static cache for string -> int conversions required for pg advisory locks. */ 50 protected static $lockidcache = array(); 51 52 /** @var \moodle_database $db Hold a reference to the global $DB */ 53 protected $db; 54 55 /** @var string $type Used to prefix lock keys */ 56 protected $type; 57 58 /** @var array $openlocks - List of held locks - used by auto-release */ 59 protected $openlocks = array(); 60 61 /** 62 * Calculate a unique instance id based on the database name and prefix. 63 * @return int. 64 */ 65 protected function get_unique_db_instance_id() { 66 global $CFG; 67 68 $strkey = $CFG->dbname . ':' . $CFG->prefix; 69 $intkey = crc32($strkey); 70 // Normalize between 64 bit unsigned int and 32 bit signed ints. Php could return either from crc32. 71 if (PHP_INT_SIZE == 8) { 72 if ($intkey > 0x7FFFFFFF) { 73 $intkey -= 0x100000000; 74 } 75 } 76 77 return $intkey; 78 } 79 80 /** 81 * Almighty constructor. 82 * @param string $type - Used to prefix lock keys. 83 */ 84 public function __construct($type) { 85 global $DB; 86 87 $this->type = $type; 88 $this->dblockid = $this->get_unique_db_instance_id(); 89 // Save a reference to the global $DB so it will not be released while we still have open locks. 90 $this->db = $DB; 91 92 \core_shutdown_manager::register_function(array($this, 'auto_release')); 93 } 94 95 /** 96 * Is available. 97 * @return boolean - True if this lock type is available in this environment. 98 */ 99 public function is_available() { 100 return $this->db->get_dbfamily() === 'postgres'; 101 } 102 103 /** 104 * Return information about the blocking behaviour of the lock type on this platform. 105 * @return boolean - Defer to the DB driver. 106 */ 107 public function supports_timeout() { 108 return true; 109 } 110 111 /** 112 * Will this lock type will be automatically released when a process ends. 113 * 114 * @return boolean - Via shutdown handler. 115 */ 116 public function supports_auto_release() { 117 return true; 118 } 119 120 /** 121 * Multiple locks for the same resource can NOT be held by a single process. 122 * 123 * @deprecated since Moodle 3.10. 124 * @return boolean - false. 125 */ 126 public function supports_recursion() { 127 debugging('The function supports_recursion() is deprecated, please do not use it anymore.', 128 DEBUG_DEVELOPER); 129 return false; 130 } 131 132 /** 133 * This function generates the unique index for a specific lock key using 134 * a sha1 prefix converted to decimal. 135 * 136 * @param string $key 137 * @return int 138 * @throws \moodle_exception 139 */ 140 protected function get_index_from_key($key) { 141 142 // A prefix of 7 hex chars is chosen as fffffff is the largest hex code 143 // which when converted to decimal (268435455) fits inside a 4 byte int 144 // which is the second param to pg_try_advisory_lock(). 145 $hash = substr(sha1($key), 0, 7); 146 $index = hexdec($hash); 147 return $index; 148 } 149 150 /** 151 * Create and get a lock 152 * 153 * @param string $resource - The identifier for the lock. Should use frankenstyle prefix. 154 * @param int $timeout - The number of seconds to wait for a lock before giving up. 155 * @param int $maxlifetime - Unused by this lock type. 156 * @return boolean - true if a lock was obtained. 157 */ 158 public function get_lock($resource, $timeout, $maxlifetime = 86400) { 159 $giveuptime = time() + $timeout; 160 161 $token = $this->get_index_from_key($this->type . '_' . $resource); 162 163 if (isset($this->openlocks[$token])) { 164 return false; 165 } 166 167 $params = [ 168 'locktype' => $this->dblockid, 169 'token' => $token 170 ]; 171 172 $locked = false; 173 174 do { 175 $result = $this->db->get_record_sql('SELECT pg_try_advisory_lock(:locktype, :token) AS locked', $params); 176 $locked = $result->locked === 't'; 177 if (!$locked && $timeout > 0) { 178 usleep(rand(10000, 250000)); // Sleep between 10 and 250 milliseconds. 179 } 180 // Try until the giveup time. 181 } while (!$locked && time() < $giveuptime); 182 183 if ($locked) { 184 $this->openlocks[$token] = 1; 185 return new lock($token, $this); 186 } 187 return false; 188 } 189 190 /** 191 * Release a lock that was previously obtained with @lock. 192 * @param lock $lock - a lock obtained from this factory. 193 * @return boolean - true if the lock is no longer held (including if it was never held). 194 */ 195 public function release_lock(lock $lock) { 196 $params = array('locktype' => $this->dblockid, 197 'token' => $lock->get_key()); 198 $result = $this->db->get_record_sql('SELECT pg_advisory_unlock(:locktype, :token) AS unlocked', $params); 199 $result = $result->unlocked === 't'; 200 if ($result) { 201 unset($this->openlocks[$lock->get_key()]); 202 } 203 return $result; 204 } 205 206 /** 207 * Extend a lock that was previously obtained with @lock. 208 * 209 * @deprecated since Moodle 3.10. 210 * @param lock $lock - a lock obtained from this factory. 211 * @param int $maxlifetime - the new lifetime for the lock (in seconds). 212 * @return boolean - true if the lock was extended. 213 */ 214 public function extend_lock(lock $lock, $maxlifetime = 86400) { 215 debugging('The function extend_lock() is deprecated, please do not use it anymore.', 216 DEBUG_DEVELOPER); 217 // Not supported by this factory. 218 return false; 219 } 220 221 /** 222 * Auto release any open locks on shutdown. 223 * This is required, because we may be using persistent DB connections. 224 */ 225 public function auto_release() { 226 // Called from the shutdown handler. Must release all open locks. 227 foreach ($this->openlocks as $key => $unused) { 228 $lock = new lock($key, $this); 229 $lock->release(); 230 } 231 } 232 233 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body