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 * File locking for the Cache API 19 * 20 * @package cachelock_file 21 * @category cache 22 * @copyright 2012 Sam Hemelryk 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 /** 29 * File locking plugin 30 * 31 * @copyright 2012 Sam Hemelryk 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 class cachelock_file implements cache_lock_interface { 35 36 /** 37 * The name of the cache lock instance 38 * @var string 39 */ 40 protected $name; 41 42 /** 43 * The absolute directory in which lock files will be created and looked for. 44 * @var string 45 */ 46 protected $cachedir; 47 48 /** 49 * The maximum life in seconds for a lock file. By default null for none. 50 * @var int|null 51 */ 52 protected $maxlife = null; 53 54 /** 55 * The number of attempts to acquire a lock when blocking is required before throwing an exception. 56 * @var int 57 */ 58 protected $blockattempts = 100; 59 60 /** 61 * An array containing the locks that have been acquired but not released so far. 62 * @var array Array of key => lock file path 63 */ 64 protected $locks = array(); 65 66 /** 67 * Initialises the cache lock instance. 68 * 69 * @param string $name The name of the cache lock 70 * @param array $configuration 71 */ 72 public function __construct($name, array $configuration = array()) { 73 $this->name = $name; 74 if (!array_key_exists('dir', $configuration)) { 75 $this->cachedir = make_cache_directory(md5($name)); 76 } else { 77 $dir = $configuration['dir']; 78 if (strpos($dir, '/') !== false && strpos($dir, '.') !== 0) { 79 // This looks like an absolute path. 80 if (file_exists($dir) && is_dir($dir) && is_writable($dir)) { 81 $this->cachedir = $dir; 82 } 83 } 84 if (empty($this->cachedir)) { 85 $dir = preg_replace('#[^a-zA-Z0-9_]#', '_', $dir); 86 $this->cachedir = make_cache_directory($dir); 87 } 88 } 89 if (array_key_exists('maxlife', $configuration) && is_number($configuration['maxlife'])) { 90 $maxlife = (int)$configuration['maxlife']; 91 // Minimum lock time is 60 seconds. 92 $this->maxlife = max($maxlife, 60); 93 } 94 if (array_key_exists('blockattempts', $configuration) && is_number($configuration['blockattempts'])) { 95 $this->blockattempts = (int)$configuration['blockattempts']; 96 } 97 } 98 99 /** 100 * Acquire a lock. 101 * 102 * If the lock can be acquired: 103 * This function will return true. 104 * 105 * If the lock cannot be acquired the result of this method is determined by the block param: 106 * $block = true (default) 107 * The function will block any further execution unti the lock can be acquired. 108 * This involves the function attempting to acquire the lock and the sleeping for a period of time. This process 109 * will be repeated until the lock is required or until a limit is hit (100 by default) in which case a cache 110 * exception will be thrown. 111 * $block = false 112 * The function will return false immediately. 113 * 114 * If a max life has been specified and the lock can not be acquired then the lock file will be checked against this time. 115 * In the case that the file exceeds that max time it will be forcefully deleted. 116 * Because this can obviously be a dangerous thing it is not used by default. If it is used it should be set high enough that 117 * we can be as sure as possible that the executing code has completed. 118 * 119 * @param string $key The key that we want to lock 120 * @param string $ownerid A unique identifier for the owner of this lock. Not used by default. 121 * @param bool $block True if we want the program block further execution until the lock has been acquired. 122 * @return bool 123 * @throws cache_exception If block is set to true and more than 100 attempts have been made to acquire a lock. 124 */ 125 public function lock($key, $ownerid, $block = false) { 126 // Get the name of the lock file we want to use. 127 $lockfile = $this->get_lock_file($key); 128 129 // Attempt to create a handle to the lock file. 130 // Mode xb is the secret to this whole function. 131 // x = Creates the file and opens it for writing. If the file already exists fopen returns false and a warning is thrown. 132 // b = Forces binary mode. 133 $result = @fopen($lockfile, 'xb'); 134 135 // Check if we could create the file or not. 136 if ($result === false) { 137 // Lock exists already. 138 if ($this->maxlife !== null && !array_key_exists($key, $this->locks)) { 139 $mtime = filemtime($lockfile); 140 if ($mtime < time() - $this->maxlife) { 141 $this->unlock($key, true); 142 $result = $this->lock($key, false); 143 if ($result) { 144 return true; 145 } 146 } 147 } 148 if ($block) { 149 // OK we are blocking. We had better sleep and then retry to lock. 150 $iterations = 0; 151 $maxiterations = $this->blockattempts; 152 while (($result = $this->lock($key, false)) === false) { 153 // Usleep causes the application to cleep to x microseconds. 154 // Before anyone asks there are 1'000'000 microseconds to a second. 155 usleep(rand(1000, 50000)); // Sleep between 1 and 50 milliseconds. 156 $iterations++; 157 if ($iterations > $maxiterations) { 158 // BOOM! We've exceeded the maximum number of iterations we want to block for. 159 throw new cache_exception('ex_unabletolock'); 160 } 161 } 162 } 163 164 return false; 165 } else { 166 // We have the lock. 167 fclose($result); 168 $this->locks[$key] = $lockfile; 169 return true; 170 } 171 } 172 173 /** 174 * Releases an acquired lock. 175 * 176 * For more details see {@link cache_lock::unlock()} 177 * 178 * @param string $key 179 * @param string $ownerid A unique identifier for the owner of this lock. Not used by default. 180 * @param bool $forceunlock If set to true the lock will be removed if it exists regardless of whether or not we own it. 181 * @return bool 182 */ 183 public function unlock($key, $ownerid, $forceunlock = false) { 184 if (array_key_exists($key, $this->locks)) { 185 @unlink($this->locks[$key]); 186 unset($this->locks[$key]); 187 return true; 188 } else if ($forceunlock) { 189 $lockfile = $this->get_lock_file($key); 190 if (file_exists($lockfile)) { 191 @unlink($lockfile); 192 } 193 return true; 194 } 195 // You cannot unlock a file you didn't lock. 196 return false; 197 } 198 199 /** 200 * Checks if the given key is locked. 201 * 202 * @param string $key 203 * @param string $ownerid 204 */ 205 public function check_state($key, $ownerid) { 206 if (array_key_exists($key, $this->locks)) { 207 // The key is locked and we own it. 208 return true; 209 } 210 $lockfile = $this->get_lock_file($key); 211 if (file_exists($lockfile)) { 212 // The key is locked and we don't own it. 213 return false; 214 } 215 return null; 216 } 217 218 /** 219 * Gets the name to use for a lock file. 220 * 221 * @param string $key 222 * @return string 223 */ 224 protected function get_lock_file($key) { 225 return $this->cachedir.'/'. $key .'.lock'; 226 } 227 228 /** 229 * Cleans up the instance what it is no longer needed. 230 */ 231 public function __destruct() { 232 foreach ($this->locks as $lockfile) { 233 // Naught, naughty developers. 234 @unlink($lockfile); 235 } 236 } 237 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body