[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 37 * done by storing the strings in the lock_db table and using the auto-id returned. There is a static cache for 38 * id's in this function. 39 * 40 * @package core 41 * @category lock 42 * @copyright Damyon Wiese 2013 43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 */ 45 class postgres_lock_factory implements lock_factory { 46 47 /** @var int $dblockid - used as a namespace for these types of locks (separate from session locks) */ 48 protected $dblockid = -1; 49 50 /** @var array $lockidcache - static cache for string -> int conversions required for pg advisory locks. */ 51 protected static $lockidcache = array(); 52 53 /** @var \moodle_database $db Hold a reference to the global $DB */ 54 protected $db; 55 56 /** @var string $type Used to prefix lock keys */ 57 protected $type; 58 59 /** @var array $openlocks - List of held locks - used by auto-release */ 60 protected $openlocks = array(); 61 62 /** 63 * Calculate a unique instance id based on the database name and prefix. 64 * @return int. 65 */ 66 protected function get_unique_db_instance_id() { 67 global $CFG; 68 69 $strkey = $CFG->dbname . ':' . $CFG->prefix; 70 $intkey = crc32($strkey); 71 // Normalize between 64 bit unsigned int and 32 bit signed ints. Php could return either from crc32. 72 if (PHP_INT_SIZE == 8) { 73 if ($intkey > 0x7FFFFFFF) { 74 $intkey -= 0x100000000; 75 } 76 } 77 78 return $intkey; 79 } 80 81 /** 82 * Almighty constructor. 83 * @param string $type - Used to prefix lock keys. 84 */ 85 public function __construct($type) { 86 global $DB; 87 88 $this->type = $type; 89 $this->dblockid = $this->get_unique_db_instance_id(); 90 // Save a reference to the global $DB so it will not be released while we still have open locks. 91 $this->db = $DB; 92 93 \core_shutdown_manager::register_function(array($this, 'auto_release')); 94 } 95 96 /** 97 * Is available. 98 * @return boolean - True if this lock type is available in this environment. 99 */ 100 public function is_available() { 101 return $this->db->get_dbfamily() === 'postgres'; 102 } 103 104 /** 105 * Return information about the blocking behaviour of the lock type on this platform. 106 * @return boolean - Defer to the DB driver. 107 */ 108 public function supports_timeout() { 109 return true; 110 } 111 112 /** 113 * Will this lock type will be automatically released when a process ends. 114 * 115 * @return boolean - Via shutdown handler. 116 */ 117 public function supports_auto_release() { 118 return true; 119 } 120 121 /** 122 * Multiple locks for the same resource can be held by a single process. 123 * @return boolean - Defer to the DB driver. 124 */ 125 public function supports_recursion() { 126 return true; 127 } 128 129 /** 130 * This function generates the unique index for a specific lock key. 131 * Once an index is assigned to a key, it never changes - so this is 132 * statically cached. 133 * 134 * @param string $key 135 * @return int 136 * @throws \moodle_exception 137 */ 138 protected function get_index_from_key($key) { 139 if (isset(self::$lockidcache[$key])) { 140 return self::$lockidcache[$key]; 141 } 142 143 $index = 0; 144 $record = $this->db->get_record('lock_db', array('resourcekey' => $key)); 145 if ($record) { 146 $index = $record->id; 147 } 148 149 if (!$index) { 150 $record = new \stdClass(); 151 $record->resourcekey = $key; 152 try { 153 $index = $this->db->insert_record('lock_db', $record); 154 } catch (\dml_exception $de) { 155 // Race condition - never mind - now the value is guaranteed to exist. 156 $record = $this->db->get_record('lock_db', array('resourcekey' => $key)); 157 if ($record) { 158 $index = $record->id; 159 } 160 } 161 } 162 163 if (!$index) { 164 throw new \moodle_exception('Could not generate unique index for key'); 165 } 166 167 self::$lockidcache[$key] = $index; 168 return $index; 169 } 170 171 /** 172 * Create and get a lock 173 * @param string $resource - The identifier for the lock. Should use frankenstyle prefix. 174 * @param int $timeout - The number of seconds to wait for a lock before giving up. 175 * @param int $maxlifetime - Unused by this lock type. 176 * @return boolean - true if a lock was obtained. 177 */ 178 public function get_lock($resource, $timeout, $maxlifetime = 86400) { 179 $giveuptime = time() + $timeout; 180 181 $token = $this->get_index_from_key($resource); 182 183 $params = array('locktype' => $this->dblockid, 184 'token' => $token); 185 186 $locked = false; 187 188 do { 189 $result = $this->db->get_record_sql('SELECT pg_try_advisory_lock(:locktype, :token) AS locked', $params); 190 $locked = $result->locked === 't'; 191 if (!$locked) { 192 usleep(rand(10000, 250000)); // Sleep between 10 and 250 milliseconds. 193 } 194 // Try until the giveup time. 195 } while (!$locked && time() < $giveuptime); 196 197 if ($locked) { 198 $this->openlocks[$token] = 1; 199 return new lock($token, $this); 200 } 201 return false; 202 } 203 204 /** 205 * Release a lock that was previously obtained with @lock. 206 * @param lock $lock - a lock obtained from this factory. 207 * @return boolean - true if the lock is no longer held (including if it was never held). 208 */ 209 public function release_lock(lock $lock) { 210 $params = array('locktype' => $this->dblockid, 211 'token' => $lock->get_key()); 212 $result = $this->db->get_record_sql('SELECT pg_advisory_unlock(:locktype, :token) AS unlocked', $params); 213 $result = $result->unlocked === 't'; 214 if ($result) { 215 unset($this->openlocks[$lock->get_key()]); 216 } 217 return $result; 218 } 219 220 /** 221 * Extend a lock that was previously obtained with @lock. 222 * @param lock $lock - a lock obtained from this factory. 223 * @param int $maxlifetime - the new lifetime for the lock (in seconds). 224 * @return boolean - true if the lock was extended. 225 */ 226 public function extend_lock(lock $lock, $maxlifetime = 86400) { 227 // Not supported by this factory. 228 return false; 229 } 230 231 /** 232 * Auto release any open locks on shutdown. 233 * This is required, because we may be using persistent DB connections. 234 */ 235 public function auto_release() { 236 // Called from the shutdown handler. Must release all open locks. 237 foreach ($this->openlocks as $key => $unused) { 238 $lock = new lock($key, $this); 239 $lock->release(); 240 } 241 } 242 243 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |