This extension can be used in case a certain part of your application should only run ONCE at a time. For example you may have a cronjob console command that is executed every minute regardless of how long the action in the cronjob takes. See Mutex article on Wikipedia.
protected/extensions'components' => array( ... 'mutex' => array( 'class' => 'application.extensions.EMutex', ), ... ),
See the following code example:
// Check if we have a lock already. If not set one which // expires automatically after 10 minutes. if (Yii::app()->mutex->lock('some-unique-id', 600)) { // Do some time-expensive stuff here... // sleep(10) as example // and after that release the lock... Yii::app()->mutex->unlock(); } else { // The lock does already exist so we exit echo "Already working on it..."; exit; }
The $timeout variable is there to ensure that the lock isn't infinite in case of things like a server crash. Means if you don't define a timeout and unlock() isn't called for some reason, the lock will stay forever.
All locks are represented in a single file. You can change the $mutexFile variable to change the path of this file (defaults to the Yii runtime path + /mutex.bin).
You can also define an $id when unlocking. That means in one cronjob you could setup a lock and in another one you could release that lock. For local created locks (current request), only unlock() works to make sure nested locks will get released in order.
Some more usage examples:
// Waiting for a lock to get released (spin lock) // Make sure to call sleep() inside of the loop, because everytime // you call lock(), the $mutexFile gets read (physical file-access). while (!Yii::app()->mutex->lock('id')) { sleep(1); } ... Yii::app()->mutex->unlock();
// Nested locks if (Yii::app()->mutex->lock('id1')) { while (!Yii::app()->mutex->lock('id2')) { sleep(1); } ... if (Yii::app()->mutex->lock('id3')) { ... Yii::app()->mutex->unlock(); // releases "id3" } ... Yii::app()->mutex->unlock(); // releases "id2" Yii::app()->mutex->unlock(); // releases "id1" }
You may use __CLASS__ as $id for example in a console command ;-)
Total 6 comments
//refreshes timeout. assumes the calling process already has the lock.
public function relock($id, $timeout = 0) {
$lockFileHandle = $this->_getLockFileHandle($id); if (flock($lockFileHandle, LOCK_EX)) { $data = @unserialize(@file_get_contents($this->mutexFile)); if (empty($data) || !isset($data[$id]) || (($data[$id][0] > 0) && ($data[$id][0] + $data[$id][1]) <= microtime(true))) { $result = false; } else { $data[$id] = array($timeout, microtime(true)); //array_push($this->_locks, $id); $result = (bool)file_put_contents($this->mutexFile, serialize($data)); } } fclose($lockFileHandle); @unlink($this->_getLockFile($id)); return isset($result) ? $result : false; }I've implemented a small function to check if a specific (or any) lock exists:
Feel free to use or even add to the extension.
Thank you for this extension!
And why not to add shell command, which flushes/removes all of the locks?
you see if there happens some error or trigger exception , the unlock will never called !
for the exception reason ,use try/catch to settle:
the php has no finally statement: try/catch/finally ; if so it's better place to unlock in finally clause;
for the trigger_error('for test too!'); you need do some effort to settle it , convert the old school php error to Exception firstly !
If your app uses MySQL database, better use MySQL named locks for that. They will unlock automatically if your thread or even the entire server happens to die prematurely, so there's no need to set a timeout. Just make sure your lock's name is unique throughout the entire MySQL server (which is not very difficult to achieve).
Simple yet useful tool for locking various tasks not to be performed twice.
Leave a comment
Please login to leave your comment.