A little explanation, I have a Symfony2 setup. I'm using an abstract command class that I extend. I want any of those batches to be able to run only once. My goal is to make a lock file which I open and flock so that the lock is automatically released when the php script dies in any way possible.
To realize this, I have created a class named Lock. This class extends SplFileObject and is basically a wrapper to create a *.lock somewhere (usually /var/lock/*). Now I have a problem detecting this lock. I did have a setup where it worked using fopen and flock. For some reason it won't detect it any more.
I have created an OOP structure to basically do what I want:
- determine a name for the lock file (uses folders)
- create a Lock object
- create the directory and lock file if they don't exist
- call the SplFileObject::__construct()
- lock the file
I can't get this to work with neither the handle nor the spl file object. If I run the script, let it sleep for 15 seconds and run the same script in another console I will get a result that the script managed to lock the file, flock returns true. If I make 2 Lock objects on the same lock file in the same script, I get true on the first lock and false on the second, meaning the second time it failed to obtain the lock. The script seems to work.
However, when I run the script 2 times with 2 locks in both scripts, I get True and false on both scripts... Meaning it seems like it doesn't lock the file properly across scripts :/
Is there someone that can tell me what I'm doing wrong? I've checked the file name and it's equal for both times I run the script. I have tried multiple permissions such as 777, 755, 733 but no difference.
The way I call it (just a part of the class):
abstract class AbstractTripolisCommand extends ContainerAwareCommand
{
[...]
/**
* Locks the current file based on environments
*
* @param string $application_env
* @param string $symfony_env
*/
private function lockCommand($application_env, $symfony_env)
{
$lock_name = "tripolis/$application_env/$symfony_env/" . crc32(get_class($this));
$lock = new Lock($lock_name, 'w+', $this->getContainer()->get('filesystem'));
var_dump($lock->lock());
$lock2 = new Lock($lock_name, 'w+', $this->getContainer()->get('filesystem'));
var_dump($lock2->lock());
// results when ran 2 times at the same time
// bool(true)
// bool(false)
// when I run this script twice I expect the second run at the same time
// bool(false)
// bool(false)
if(!$lock->lock()) {
throw new Tripolis\Exception('Unable to obtain lock, script is already running');
}
}
[...]
}
Lock.php
namespace Something\Component\File;
use Symfony\Component\Filesystem\Filesystem;
/**
* Creates a new SplFileObject with access to lock and release locks
* Upon creation it will create the lock file if not exists
*
* The lock works by keeping a stream open to the lock file instead
* of creating/deleting the lock file. This way the lock is always
* released when the script ends or crashes.
*
* create a file named /var/lock/something/something.lock
* <example>
* $lock = new Lock('something');
* $lock->lock();
* $lock->release();
* </example>
*
* create a file named /var/lock/something/my-lock-file.lock
* <example>
* $lock = new Lock('something/my-lock-file');
* $lock->lock();
* </example>
*
* NOTE: locks that are not released are released
* automatically when the php script ends
*
* @author Iltar van der Berg <ivanderberg@something.nl>
*/
class Lock extends \SplFileObject implements Lockable
{
/**
* @param string $file_name
* @param string $open_mode
* @param Filesystem $filesystem
* @param string $lock_directory
*/
public function __construct($file_name, $open_mode = 'r+', Filesystem $filesystem = null, $lock_directory = '/var/lock')
{
$filesystem = $filesystem ?: new Filesystem();
$file = self::touchLockFile($file_name, $lock_directory, $filesystem);
parent::__construct($file, $open_mode);
}
/**
* Returns true if the lock is placed, false if unable to
*
* @return boolean
*/
public function lock()
{
return $this->flock(LOCK_EX | LOCK_NB);
}
/**
* Returns true if the lock is released
*
* @return bool
*/
public function release()
{
return $this->flock(LOCK_UN);
}
/**
* Attempts to create a lock file for a given filename and directory
* it will return a string if the file is touched
*
* @param string $file_name
* @param string $lock_directory
* @param Filesystem $filesystem
* @return string
*/
private static function touchLockFile($file_name, $lock_directory, Filesystem $filesystem)
{
$lock_file_path = explode('/', $file_name);
$lock_file = array_pop($lock_file_path);
$path = "$lock_directory/" . (empty($lock_file_path)
? $lock_file
: implode('/', $lock_file_path));
$lock_file = "$path/$lock_file.lock";
if(!$filesystem->exists($path) || !is_dir($path)) {
$filesystem->mkdir($path);
}
return $lock_file;
}
}
?>