<?php
namespace App\Cron\Mutex; class FLockWithHash implements AbstractLock { private $handle = null; private $file = null; private $lockhash = null; private $isLockedReasons = []; public function __construct($id) { $CFG = \Engine\Loader_Cfg::getInstance()->getGlobal(); $this->setFile(sprintf("%s%s%s.lock", APP_REAL_PATH, $CFG['paths']['temporary'], $id)); } protected function setFile($file) { $this->file = $file; return $this; } public function lock() { if (!file_exists($this->file)) { file_put_contents($this->file, ''); chmod($this->file, 0777); } $this->handle = fopen($this->file, 'cb+'); if (!$this->handle) { throw new \RuntimeException(sprintf("Cannot open %s, for locking", $this->file)); } $locked = flock($this->handle, LOCK_EX); if (!$locked) { throw new \RuntimeException(sprintf("Cannot lock %s", $this->file)); } $this->lockhash = sprintf("%s:% 9s", \Engine\RandomGenerator::randomString(16), getmypid()); fwrite($this->handle, $this->lockhash); return $this; } public function unlock() { if ($this->handle) { flock($this->handle, LOCK_UN); fclose($this->handle); $this->handle = null; } $this->lockhash = null; return $this; } public function isLocked() { $this->isLockedReasons = []; clearstatcache(false, $this->file); if (!file_exists($this->file)) { $this->isLockedReasons = [ 'fileExists' => false, ]; return false; } if ($this->handle) { fseek($this->handle, 0, SEEK_SET); $lockhash = fread($this->handle, 256); $fstat = fstat($this->handle); $locked = ($this->handle!==null && $lockhash===$this->lockhash && $fstat['nlink']!==0); if ($locked) { return true; } else { $this->isLockedReasons = [ 'this->handle' => ($this->handle!==null), 'this->lockhash' => $this->lockhash, 'lockhash' => $lockhash, 'fstat->nlink' => $fstat['nlink'], ]; return false; } } $fileIsLocked = false; $handle = fopen($this->file, 'rb'); if (!$handle) { $this->isLockedReasons = [ 'fopen:failed' => true, ]; return true; } $wouldBlock = null; $lockSucceeded = flock($handle, LOCK_EX | LOCK_NB, $wouldBlock); if (!$lockSucceeded || $wouldBlock) { $fileIsLocked = true; flock($handle, LOCK_UN); } $this->isLockedReasons = [ 'flock, interpreted' => !$fileIsLocked, 'lockhash' => fread($handle, 256), 'lockSucceeded' => $lockSucceeded, 'wouldBlock' => $wouldBlock, ]; fclose($handle); return $fileIsLocked; } public function isLockedWithTimeout($timeoutMin=0.0, $timeoutMax=0.5, $loopsMin=1, $loopsMax=PHP_INT_MAX) { $keepWaiting = true; $loopStart = microtime(true); $locked = true; $loops = 0; do { if ($keepWaiting && !$this->isLocked()) { $locked = false; } if ($timeoutMax!==null && $keepWaiting && (microtime(true) - $loopStart)>=$timeoutMax) { $keepWaiting = false; } if ($loopsMax!==null && $keepWaiting && $loops>=$loopsMax) { $keepWaiting = false; } if ($loopsMin!==null && !$keepWaiting && $loops<$loopsMin) { $keepWaiting = true; } if ($timeoutMin!==null && !$keepWaiting && (microtime(true) - $loopStart)<$timeoutMin) { $keepWaiting = true; } if ($keepWaiting && $locked) { usleep(1); } $loops++; } while ($keepWaiting && $locked); return $locked; } public function rawGetIsLockedReasons() { return $this->isLockedReasons; } public function wasAquired() { return (bool)$this->handle; } public function lockWithTimeout($timeout) { $keepWaiting = true; $loopStart = microtime(true); $locked = false; do { if ($keepWaiting && !$this->isLocked()) { $keepWaiting = false; $locked = true; $this->lock(); } if ($keepWaiting && (microtime(true) - $loopStart)>=$timeout) { $keepWaiting = false; } if ($keepWaiting) { usleep(1); } } while ($keepWaiting); return $locked; } public function removeLock() { unlink($this->file); return $this; } } 