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