| 1142 | | static protected $locks = array(); |
| 1143 | | |
| 1144 | | /** |
| 1145 | | * Lock names must be \w+ |
| 1146 | | * @param mixed $name |
| | 1142 | static protected $lockService = null; |
| | 1143 | |
| | 1144 | /** |
| | 1145 | * Lock names must be ascii alphanumeric and underscore characters |
| | 1146 | * and must not be empty. If $wait is false, tries to get a lock |
| | 1147 | * and returns false promptly if it is unable to do so right now, |
| | 1148 | * true on success. If $wait is true, returns true on a successful |
| | 1149 | * lock after waiting up to 30 seconds, otherwise throws an sfException. |
| | 1150 | * |
| | 1151 | * By default an implementation based on flock() is used, which is fine |
| | 1152 | * for the vast majority of sites (see aLockFlock for details). If you |
| | 1153 | * wish to use an alternative implementation, set app_a_lock_class to |
| | 1154 | * the name of any implementation of our aLock interface, and set |
| | 1155 | * app_a_lock_options to contain any necessary options to initialize it. |
| | 1156 | * |
| | 1157 | * @param string $name |
| | 1158 | * @param bool $wait |
| 1150 | | $lockType = sfConfig::get('app_a_lock_type', 'flock'); |
| 1151 | | if ($lockType === 'mysql') |
| 1152 | | { |
| 1153 | | $info = aTools::getLockDbInfo(); |
| 1154 | | $dbName = $info['dbName']; |
| 1155 | | $sql = $info['sql']; |
| 1156 | | $key = "$dbName-$name"; |
| 1157 | | if ($wait) |
| 1158 | | { |
| 1159 | | $timeout = 30; |
| 1160 | | } |
| 1161 | | else |
| 1162 | | { |
| 1163 | | $timeout = 0; |
| 1164 | | } |
| 1165 | | $locked = $sql->queryOneScalar('SELECT GET_LOCK(:key, :timeout)', array('key' => $key, 'timeout' => $timeout)); |
| 1166 | | if (!$locked) |
| 1167 | | { |
| 1168 | | if (!$wait) |
| 1169 | | { |
| 1170 | | return false; |
| 1171 | | } |
| 1172 | | else |
| 1173 | | { |
| 1174 | | throw new sfException("Unable to obtain a lock after 30 seconds. MySQL must be very busy."); |
| 1175 | | } |
| 1176 | | } |
| 1177 | | aTools::$locks[] = $name; |
| 1178 | | return true; |
| 1179 | | } |
| 1180 | | else |
| 1181 | | { |
| 1182 | | $dir = aFiles::getWritableDataFolder(array('a', 'locks')); |
| 1183 | | if (!preg_match('/^\w+$/', $name)) |
| 1184 | | { |
| 1185 | | throw new sfException("Lock name is empty or contains non-word characters"); |
| 1186 | | } |
| 1187 | | $file = "$dir/$name.lck"; |
| 1188 | | $tries = 0; |
| 1189 | | while (true) |
| 1190 | | { |
| 1191 | | $fp = fopen($file, 'a'); |
| 1192 | | if ($fp) |
| 1193 | | { |
| 1194 | | $flags = LOCK_EX; |
| 1195 | | if (!$wait) |
| 1196 | | { |
| 1197 | | $flags |= LOCK_NB; |
| 1198 | | } |
| 1199 | | if (flock($fp, $flags)) |
| 1200 | | { |
| 1201 | | break; |
| 1202 | | } |
| 1203 | | } |
| 1204 | | if (!$wait) |
| 1205 | | { |
| 1206 | | return false; |
| 1207 | | } |
| 1208 | | $tries++; |
| 1209 | | if ($tries == 30) |
| 1210 | | { |
| 1211 | | throw new sfException("Unable to obtain a lock after 30 seconds. Set app_a_lockType to mysql or make sure $dir is on a filesystem that supports flock() calls."); |
| 1212 | | } |
| 1213 | | sleep(1); |
| 1214 | | } |
| 1215 | | aTools::$locks[] = $fp; |
| 1216 | | return true; |
| 1217 | | } |
| 1218 | | } |
| 1219 | | |
| 1220 | | static protected $lockSql; |
| 1221 | | static protected $dbName; |
| 1222 | | |
| 1223 | | /** |
| 1224 | | * Connect to the lock database and return an array containing |
| 1225 | | * dbName (the name of the *main* database for this site) and |
| 1226 | | * sql (an aMysql object ready to talk to the *lock* database, which |
| 1227 | | * can be separate). We need dbName to namespace our locks properly |
| 1228 | | * |
| 1229 | | * If app_a_lock_db is unspecified we use the default database |
| 1230 | | * for locks too. IF THIS DATABASE IS A MASTER/SLAVE SETUP, LOCKS WILL NOT |
| 1231 | | * BE PROPERLY MANAGED AND YOUR PAGE TREE WILL NOT BE SAFE. If this is a problem |
| 1232 | | * for you, set up a separate mysql instance that is NOT master/slave as a lockserver |
| 1233 | | * and specify the credentials for it in app_a_lock_db, giving the keys |
| 1234 | | * host, user and password. No database name is required. |
| 1235 | | * |
| 1236 | | * Results are cached between calls during the same PHP request. Otherwise |
| 1237 | | * we'd have separate connections to the lockdb and be unable to release what |
| 1238 | | * we locked |
| 1239 | | */ |
| 1240 | | |
| 1241 | | static public function getLockDbInfo() |
| 1242 | | { |
| 1243 | | if (!isset(aTools::$lockSql)) |
| 1244 | | { |
| 1245 | | $mainSql = new aMysql(); |
| 1246 | | $dbName = $mainSql->queryOneScalar('SELECT DATABASE()'); |
| 1247 | | if (!strlen($dbName)) |
| 1248 | | { |
| 1249 | | throw new sfException('Unable to get name of main database for lock namespacing purposes'); |
| 1250 | | } |
| 1251 | | $params = sfConfig::get('app_a_lock_db'); |
| 1252 | | if ($params) |
| 1253 | | { |
| 1254 | | $pdo = new PDO('mysql:host=' . $params['host'], $params['user'], $params['password']); |
| 1255 | | if (!$pdo) |
| 1256 | | { |
| 1257 | | throw new sfException('Unable to connect to lock db specified by app_a_lock_db'); |
| 1258 | | } |
| 1259 | | $sql = new aMysql($pdo); |
| 1260 | | } |
| 1261 | | else |
| 1262 | | { |
| 1263 | | // OK, use the main database. This is NOT SAFE if you use MySQL replication |
| 1264 | | $sql = new $mainSql; |
| 1265 | | } |
| 1266 | | aTools::$dbName = $dbName; |
| 1267 | | aTools::$lockSql = $sql; |
| 1268 | | } |
| 1269 | | return array('dbName' => aTools::$dbName, 'sql' => aTools::$lockSql); |
| 1270 | | } |
| 1271 | | |
| 1272 | | /** |
| 1273 | | * Release the most recent lock |
| | 1162 | if (!aTools::$lockService) |
| | 1163 | { |
| | 1164 | $class = sfConfig::get('app_a_lock_class', 'aLockFlock'); |
| | 1165 | $options = sfConfig::get('app_a_lock_options', array()); |
| | 1166 | aTools::$lockService = new $class($options); |
| | 1167 | } |
| | 1168 | $result = aTools::$lockService->lock($name, $wait); |
| | 1169 | if ($result) |
| | 1170 | { |
| | 1171 | error_log("Locked $name"); |
| | 1172 | } |
| | 1173 | return $result; |
| | 1174 | } |
| | 1175 | |
| | 1176 | /** |
| | 1177 | * Release the most recent lock. It is not an error to call with no locks |
| 1277 | | $lockType = sfConfig::get('app_a_lock_type', 'flock'); |
| 1278 | | |
| 1279 | | if ($lockType === 'mysql') |
| 1280 | | { |
| 1281 | | if (count(aTools::$locks)) |
| 1282 | | { |
| 1283 | | $name = array_pop(aTools::$locks); |
| 1284 | | $info = aTools::getLockDbInfo(); |
| 1285 | | $dbName = $info['dbName']; |
| 1286 | | $sql = $info['sql']; |
| 1287 | | $key = "$dbName-$name"; |
| 1288 | | $sql->queryOneScalar('SELECT RELEASE_LOCK(:key)', array('key' => $key)); |
| 1289 | | } |
| 1290 | | } |
| 1291 | | else |
| 1292 | | { |
| 1293 | | if (count(aTools::$locks)) |
| 1294 | | { |
| 1295 | | $fp = array_pop(aTools::$locks); |
| 1296 | | fclose($fp); |
| 1297 | | } |
| 1298 | | else |
| 1299 | | { |
| 1300 | | // It's OK to call with no lock, this greatly simplifies methods like flunkUnless() |
| 1301 | | // If you are using multiple names you are responsible for making sure you unlock consistently |
| 1302 | | } |
| 1303 | | } |
| | 1181 | if (!isset(aTools::$lockService)) |
| | 1182 | { |
| | 1183 | /** |
| | 1184 | * It is not an error to call with no locks |
| | 1185 | */ |
| | 1186 | return; |
| | 1187 | } |
| | 1188 | aTools::$lockService->unlock(); |
| | 1189 | error_log("Unlocked"); |