Yii 1.1: mutex

Provides a locking mechanism with timeout functionality
14 followers

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.

Resources

Documentation

Requirements

  • Yii 1.0 or above

Installation

  • Extract the release file under protected/extensions
  • Define component in config
'components' => array(
   ...
   'mutex' => array(
      'class' => 'application.extensions.EMutex',
   ),
   ...
),

Usage

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 ;-)

Change Log

April 12, 2010

  • Rewritten. Now using one file to store all the locks. See forum discussion and updated documentation for more info.

April 3, 2010

  • The application name is now considered for the unqiue lock file names. So it's possible to use one lock file directory for different applications without conflicts.

March 29, 2010

  • Initial release.

Total 6 comments

#8767 report it
avner at 2012/06/25 10:38am
I added a quick-and-dirty "relock" method that restarts the timeout

//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;

}
#7701 report it
Renan at 2012/04/10 02:16pm
Implement Lock check

I've implemented a small function to check if a specific (or any) lock exists:

/*
     * Verify wheter a lock exists or not.
     * 
     * @param mixed $id The optional lock id to verify
     * @return boolean If the locks exists
     */
    public function lockExists($id = null) {
        if($id === null)
            if(count($this->_locks) == 0)
                return false;
            else
                return true;
        else
            if(in_array($id, $this->_locks))
                return true;
            else
                return false;
    }
}

Feel free to use or even add to the extension.

#7138 report it
resurtm at 2012/02/27 12:20am
Improvement

Thank you for this extension!

And why not to add shell command, which flushes/removes all of the locks?

#5876 report it
yiqing95 at 2011/11/22 01:33am
when exception happens from the locked code segment , what 's your advise?
// 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'))
{
 
   // Do some time-expensive stuff here...
   // sleep(10) as example
 
             throw  new Exception('for test !');
 
            trigger_error('for  test too!');
 
 
   // 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;
}

you see if there happens some error or trigger exception , the unlock will never called !
for the exception reason ,use try/catch to settle:

if (Yii::app()->mutex->lock('someId')) {
          //always  use  try/catch blok
           try{
               throw  new Exception('for test !');
               //   trigger_error('for  test too!');
 
            }catch( Exception $e){
                Yii::log('exception happens  in method'.__METHOD__);
                 Yii::app()->mutex->unlock();
                throw $e; //we  just  rethrow the exception instance
            }
             echo 'hi  this is  in locked code segment';
            // 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 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 !

#482 report it
sova at 2010/05/18 02:41pm
other way to do the same

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).

#648 report it
samdark at 2010/03/29 06:49pm
Good one

Simple yet useful tool for locking various tasks not to be performed twice.

Leave a comment

Please to leave your comment.

Create extension
  • Yii Version: 1.1
  • License: Other Open Source License
  • Developed by: Y!!
  • Category: Others
  • Votes: +13
  • Downloaded: 1,255 times
  • Created on: Mar 29, 2010
  • Last updated: Apr 12, 2010