Doan Cuong Doan Cuong - 6 months ago 24
PHP Question

Lock process in symfony 1.4

I need a way to prevent multiple run on a process on symfony 1.4.

Something like: when a user is running this process, the other user who tries to run this process will get a warning message inform that the process is running.

Is there a way to implement it without using database?

Answer

kirugan's method will probably work in most cases, but it's susceptible to race conditions and can get stuck in the event of a crash.

Here's a more robust solution. It uses PHP's file locking so you know the lock is atomic, and if you forget to release the lock later or your process crashes, it gets released automatically. By default, getting a lock is non-blocking (i.e. if the lock is already held by another process, getLock() will instantly return FALSE). However, you can have the call block (i.e. wait until the lock becomes available) if you'd like. Finally, you can have different locks for different parts of your code. Just use a different name for the lock.

The only requirement is that the directory returned by getLockDir() must be server-writable. Feel free to change the location of the lock dir.

Note: I think flock() may behave differently on Windows (I use linux), so double check that if its an issue for you.

myLock.class.php

class myLock
{
  /**
   * Creates a lockfile and acquires an exclusive lock on it.
   *
   * @param string $filename The name of the lockfile.
   * @param boolean $blocking Block until lock becomes available (default: don't block, just fail)
   * @return mixed Returns the lockfile, or FALSE if a lock could not be acquired.
   */
  public static function getLock($name, $blocking = false)
  {
    $filename = static::getLockDir() . '/' . $name;
    if (!preg_match('/\.lo?ck$/', $filename))
    {
      $filename .= '.lck';
    }
    if (!file_exists($filename))
    {
      file_put_contents($filename, '');
      chmod($filename, 0777); // if the file cant be opened for writing later, getting the lock will fail
    }
    $lockFile = fopen($filename, 'w+');
    if (!flock($lockFile, $blocking ? LOCK_EX : LOCK_EX|LOCK_NB))
    {
      fclose($lockFile);
      return false;
    }
    return $lockFile;
  }

  /**
   * Free a lock.
   *
   * @param resource $lockFile
   */
  public static function freeLock($lockFile)
  {
    if ($lockFile)
    {
      flock($lockFile, LOCK_UN);
      fclose($lockFile);
    }
  }

  public static function getLockDir()
  {
    return sfConfig::get('sf_root_dir') . '/data/lock';
  }
}

How to use

$lockFile = myLock::getLock('lock-name');
if ($lockFile) {

  // you have a lock here, do whatever you want

  myLock::freeLock($lockFile);
}
else {
  // you could not get the lock. show a message or throw an exception or whatever
}
Comments