See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 * This is a db record 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 * This is a db record locking factory. 32 * 33 * This lock factory uses record locks relying on sql of the form "SET XXX where YYY" and checking if the 34 * value was set. It supports timeouts, autorelease and can work on any DB. The downside - is this 35 * will always be slower than some shared memory type locking function. 36 * 37 * @package core 38 * @category lock 39 * @copyright Damyon Wiese 2013 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class db_record_lock_factory implements lock_factory { 43 44 /** @var \moodle_database $db Hold a reference to the global $DB */ 45 protected $db; 46 47 /** @var string $type Used to prefix lock keys */ 48 protected $type; 49 50 /** @var array $openlocks - List of held locks - used by auto-release */ 51 protected $openlocks = array(); 52 53 /** 54 * Is available. 55 * @return boolean - True if this lock type is available in this environment. 56 */ 57 public function is_available() { 58 return true; 59 } 60 61 /** 62 * Almighty constructor. 63 * @param string $type - Used to prefix lock keys. 64 */ 65 public function __construct($type) { 66 global $DB; 67 68 $this->type = $type; 69 // Save a reference to the global $DB so it will not be released while we still have open locks. 70 $this->db = $DB; 71 72 \core_shutdown_manager::register_function(array($this, 'auto_release')); 73 } 74 75 /** 76 * Return information about the blocking behaviour of the lock type on this platform. 77 * @return boolean - True 78 */ 79 public function supports_timeout() { 80 return true; 81 } 82 83 /** 84 * Will this lock type will be automatically released when a process ends. 85 * 86 * @return boolean - True (shutdown handler) 87 */ 88 public function supports_auto_release() { 89 return true; 90 } 91 92 /** 93 * Multiple locks for the same resource can be held by a single process. 94 * @return boolean - False - not process specific. 95 */ 96 public function supports_recursion() { 97 return false; 98 } 99 100 /** 101 * This function generates a unique token for the lock to use. 102 * It is important that this token is not solely based on time as this could lead 103 * to duplicates in a clustered environment (especially on VMs due to poor time precision). 104 */ 105 protected function generate_unique_token() { 106 return \core\uuid::generate(); 107 } 108 109 /** 110 * Create and get a lock 111 * @param string $resource - The identifier for the lock. Should use frankenstyle prefix. 112 * @param int $timeout - The number of seconds to wait for a lock before giving up. 113 * @param int $maxlifetime - Unused by this lock type. 114 * @return boolean - true if a lock was obtained. 115 */ 116 public function get_lock($resource, $timeout, $maxlifetime = 86400) { 117 118 $token = $this->generate_unique_token(); 119 $now = time(); 120 $giveuptime = $now + $timeout; 121 $expires = $now + $maxlifetime; 122 123 $resourcekey = $this->type . '_' . $resource; 124 125 if (!$this->db->record_exists('lock_db', array('resourcekey' => $resourcekey))) { 126 $record = new \stdClass(); 127 $record->resourcekey = $resourcekey; 128 $result = $this->db->insert_record('lock_db', $record); 129 } 130 131 $params = array('expires' => $expires, 132 'token' => $token, 133 'resourcekey' => $resourcekey, 134 'now' => $now); 135 $sql = 'UPDATE {lock_db} 136 SET 137 expires = :expires, 138 owner = :token 139 WHERE 140 resourcekey = :resourcekey AND 141 (owner IS NULL OR expires < :now)'; 142 143 do { 144 $now = time(); 145 $params['now'] = $now; 146 $this->db->execute($sql, $params); 147 148 $countparams = array('owner' => $token, 'resourcekey' => $resourcekey); 149 $result = $this->db->count_records('lock_db', $countparams); 150 $locked = $result === 1; 151 if (!$locked && $timeout > 0) { 152 usleep(rand(10000, 250000)); // Sleep between 10 and 250 milliseconds. 153 } 154 // Try until the giveup time. 155 } while (!$locked && $now < $giveuptime); 156 157 if ($locked) { 158 $this->openlocks[$token] = 1; 159 return new lock($token, $this); 160 } 161 162 return false; 163 } 164 165 /** 166 * Release a lock that was previously obtained with @lock. 167 * @param lock $lock - a lock obtained from this factory. 168 * @return boolean - true if the lock is no longer held (including if it was never held). 169 */ 170 public function release_lock(lock $lock) { 171 $params = array('noexpires' => null, 172 'token' => $lock->get_key(), 173 'noowner' => null); 174 175 $sql = 'UPDATE {lock_db} 176 SET 177 expires = :noexpires, 178 owner = :noowner 179 WHERE 180 owner = :token'; 181 $result = $this->db->execute($sql, $params); 182 if ($result) { 183 unset($this->openlocks[$lock->get_key()]); 184 } 185 return $result; 186 } 187 188 /** 189 * Extend a lock that was previously obtained with @lock. 190 * @param lock $lock - a lock obtained from this factory. 191 * @param int $maxlifetime - the new lifetime for the lock (in seconds). 192 * @return boolean - true if the lock was extended. 193 */ 194 public function extend_lock(lock $lock, $maxlifetime = 86400) { 195 $now = time(); 196 $expires = $now + $maxlifetime; 197 $params = array('expires' => $expires, 198 'token' => $lock->get_key()); 199 200 $sql = 'UPDATE {lock_db} 201 SET 202 expires = :expires, 203 WHERE 204 owner = :token'; 205 206 $this->db->execute($sql, $params); 207 $countparams = array('owner' => $lock->get_key()); 208 $result = $this->count_records('lock_db', $countparams); 209 210 return $result === 0; 211 } 212 213 /** 214 * Auto release any open locks on shutdown. 215 * This is required, because we may be using persistent DB connections. 216 */ 217 public function auto_release() { 218 // Called from the shutdown handler. Must release all open locks. 219 foreach ($this->openlocks as $key => $unused) { 220 $lock = new lock($key, $this); 221 $lock->release(); 222 } 223 } 224 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body