PHP\'s documentation page for flock() indicates that it\'s not safe to use under IIS. If I can\'t rely on flock under all circumstances, is there another way I
Here is my "PHP flock() alternative" - build on
mkdir().
The idea to do it with mkdir() came from here and here.
My version
You can use the PHP-class like this:
//$dir (string) = base-directory for the lock-files (with 'files' I mean directories => mode 0644)
// 2 (float/int) = time to wait for lock-access before returning unsuccessful (default is 0 <= try once and return)
//'.my_lock' (string) = the way you want to name your locking-dirs (default is '.fLock')
$lock = new FileLock($dir, 2, '.my_lock');
//start lock - a locking directory will be created looking like this:
//$dir/.my_lock-1536166146.4997-22796
if ($lock->lock()) {
//open your file - modify it - write it back
} else { /* write alert-email to admin */ }
//check if I had locked before
if ($lock->is_locked) { /* do something else with your locked file */ }
//unlock - the created dir will be removed (rmdir)
$lock->unlock();
Here is the working class:
//build a file-locking class
define('LOCKFILE_NONE', 0);
define('LOCKFILE_LOCKED', 1);
define('LOCKFILE_ALREADY_LOCKED', 2);
define('LOCKFILE_ALREADY_LOCKED_IN_OTHER_CLASS', 3);
define('LOCKFILE_FAILED_TO_OBTAIN_LOCK', false);
define('LOCKFILE_FAILED_TO_OBTAIN_LOCK_BY_TIMEOUT', '');
class FileLock {
//FileLock assumes that there are no other directories or files in the
//lock-base-directory named "$name-(float)-(int)"
//FileLock uses mkdir() to lock. Why?
//- mkdir() is atomic, so the lock is atomic and faster then saving files.
// Apparently it is faster than flock(), that requires several calls to the
// file system.
//- flock() depends on the system, mkdir() works everywhere.
private static $locked_memory = array();
public function __construct($lockbasedir, $wait_sec=0, $name='.fLock') {
$this->lockbasedir = (string)$lockbasedir;
$this->wait = (float)$wait_sec;
$this->name = (string)$name;
$this->pid = (int)getmypid();
//if this basedir.name was locked before and is still locked don't try to lock again
$this->is_locked = empty(self::$locked_memory[$this->lockbasedir . $this->name]) ? LOCKFILE_NONE : LOCKFILE_ALREADY_LOCKED;
}
public function lock() {
if ($this->is_locked) return $this->is_locked;
$break_time = microtime(true);
//create the directory as lock-file NOW
$this->lockdir = "{$this->name}-" . number_format($break_time, 4, '.', '') . "-{$this->pid}";
@mkdir("{$this->lockbasedir}/{$this->lockdir}", 0644);
$break_time += $this->wait;
//try to get locked
while ($this->wait == 0 || microtime(true) < $break_time) {
//get all locks with $this->name
$files = preg_grep("/^{$this->name}-\d+\.\d+-\d+$/", scandir($this->lockbasedir));
//since scandir() is sorted asc by default
//$first_file is the next directory to obtain lock
$first_file = reset($files);
if (!$first_file) {
//no lock-files at all
return $this->is_locked = LOCKFILE_FAILED_TO_OBTAIN_LOCK;
} elseif ($first_file == $this->lockdir) {
//Its me!! I'm getting locked :)
self::$locked_memory[$this->lockbasedir . $this->name] = 1;
return $this->is_locked = LOCKFILE_LOCKED;
} elseif (preg_match("/^{$this->name}-\d+\.\d+-{$this->pid}$/", $first_file)) {
//my process-ID already locked $this->name in another class before
rmdir("{$this->lockbasedir}/{$this->lockdir}");
$this->lockdir = $first_file;
self::$locked_memory[$this->lockbasedir . $this->name] = 1;
return $this->is_locked = LOCKFILE_ALREADY_LOCKED_IN_OTHER_CLASS;
}
//missing lock-file for this job
if (array_search($this->lockdir, $files) === false) return LOCKFILE_FAILED_TO_OBTAIN_LOCK;
//run only once
if ($this->wait == 0) break;
//check if process at first place has died
if (!posix_getsid(explode('-', $first_file)[2])) {
//remove dead lock
@rmdir("{$this->lockbasedir}/$first_file");
} else {
//wait and try again after 0.1 seconds
usleep(100000);
}
}
return $this->is_locked = LOCKFILE_FAILED_TO_OBTAIN_LOCK_BY_TIMEOUT;
}
public function unlock($force=false) {
if ($force || $this->is_locked == 1) {
rmdir("{$this->lockbasedir}/{$this->lockdir}");
self::$locked_memory[$this->lockbasedir . $this->name] = $this->is_locked = LOCKFILE_NONE;
}
}
}