1a6d63b89Sgerardnico<?php 2a6d63b89Sgerardnico 3a6d63b89Sgerardniconamespace ComboStrap; 4a6d63b89Sgerardnico 5a6d63b89Sgerardnico 6a6d63b89Sgerardnicouse dokuwiki\Search\Indexer; 7a6d63b89Sgerardnico 8a6d63b89Sgerardnico/** 9a6d63b89Sgerardnico * Adapted from the {@link Indexer::lock()} 10a6d63b89Sgerardnico * because the TaskRunner does not run serially 11a6d63b89Sgerardnico * Only the indexer does 12a6d63b89Sgerardnico * https://forum.dokuwiki.org/d/21044-taskrunner-running-multiple-times-eating-the-memory-lock 13*ea72c629Sgerardnico * 14*ea72c629Sgerardnico * Example with debug 15*ea72c629Sgerardnico * ``` 16*ea72c629Sgerardnico * ComboLockTaskRunner(): Trying to get a lock 17*ea72c629Sgerardnico * ComboLockTaskRunner(): Locked 18*ea72c629Sgerardnico * runIndexer(): started 19*ea72c629Sgerardnico * Indexer: index for web:browser:selection up to date 20*ea72c629Sgerardnico * runSitemapper(): started 21*ea72c629Sgerardnico * runSitemapper(): finished 22*ea72c629Sgerardnico * sendDigest(): started 23*ea72c629Sgerardnico * sendDigest(): disabled 24*ea72c629Sgerardnico * runTrimRecentChanges(): started 25*ea72c629Sgerardnico * runTrimRecentChanges(): finished 26*ea72c629Sgerardnico * runTrimRecentChanges(1): started 27*ea72c629Sgerardnico * runTrimRecentChanges(1): finished 28*ea72c629Sgerardnico * ComboDispatchEvent(): Trying to get a lock 29*ea72c629Sgerardnico * ComboDispatchEvent(): Locked 30*ea72c629Sgerardnico * ComboDispatchEvent(): Lock Released 31*ea72c629Sgerardnico * ComboLockTaskRunner(): Lock Released 32*ea72c629Sgerardnico * 33a6d63b89Sgerardnico */ 34a6d63b89Sgerardnicoclass Lock 35a6d63b89Sgerardnico{ 36a6d63b89Sgerardnico private string $lockName; 37a6d63b89Sgerardnico private string $lockFile; 38a6d63b89Sgerardnico /** 39a6d63b89Sgerardnico * @var mixed|null 40a6d63b89Sgerardnico */ 41a6d63b89Sgerardnico private $perm; 429fc13f00Sgerardnico private int $timeOut = 5; 432becc3acSgerardnico /** 442becc3acSgerardnico * @var false|resource 452becc3acSgerardnico */ 462becc3acSgerardnico private $filePointer = null; 47a6d63b89Sgerardnico 48a6d63b89Sgerardnico 49a6d63b89Sgerardnico /** 50a6d63b89Sgerardnico * @param string $name 51a6d63b89Sgerardnico */ 52a6d63b89Sgerardnico public function __construct(string $name) 53a6d63b89Sgerardnico { 54a6d63b89Sgerardnico $this->lockName = $name; 55a6d63b89Sgerardnico global $conf; 56a6d63b89Sgerardnico $this->lockFile = $conf['lockdir'] . "/_{$this->lockName}.lock"; 57a6d63b89Sgerardnico $this->perm = $conf['dperm'] ?? null; 58a6d63b89Sgerardnico } 59a6d63b89Sgerardnico 60a6d63b89Sgerardnico public static function create(string $name): Lock 61a6d63b89Sgerardnico { 62a6d63b89Sgerardnico return new Lock($name); 63a6d63b89Sgerardnico } 64a6d63b89Sgerardnico 65a6d63b89Sgerardnico /** 66a6d63b89Sgerardnico * @throws ExceptionTimeOut - with the timeout 67a6d63b89Sgerardnico */ 689fc13f00Sgerardnico function acquire(): Lock 69a6d63b89Sgerardnico { 70a6d63b89Sgerardnico $run = 0; 712becc3acSgerardnico /** 722becc3acSgerardnico * The flock function follows the semantics of the Unix system call bearing the same name. 732becc3acSgerardnico * Flock utilizes ADVISORY locking only; that is: 742becc3acSgerardnico * * other processes may ignore the lock completely it only affects those that call the flock call. 752becc3acSgerardnico * 762becc3acSgerardnico * * LOCK_SH means SHARED LOCK. Any number of processes MAY HAVE A SHARED LOCK simultaneously. It is commonly called a reader lock. 772becc3acSgerardnico * * LOCK_EX means EXCLUSIVE LOCK. Only a single process may possess an exclusive lock to a given file at a time. 782becc3acSgerardnico * 792becc3acSgerardnico * ie if the file has been LOCKED with LOCK_SH in another process, 802becc3acSgerardnico * * flock with LOCK_SH will SUCCEED. 812becc3acSgerardnico * * flock with LOCK_EX will BLOCK UNTIL ALL READER LOCKS HAVE BEEN RELEASED. 822becc3acSgerardnico * 832becc3acSgerardnico * When the file is closed, the lock is released by the system anyway. 842becc3acSgerardnico */ 852becc3acSgerardnico // LOCK_NB to not block the process 862becc3acSgerardnico while (!$this->getLock()) { 87*ea72c629Sgerardnico sleep(1); 88a6d63b89Sgerardnico /** 892becc3acSgerardnico * Old lock ? More than 10 minutes run 90a6d63b89Sgerardnico */ 912becc3acSgerardnico if (is_file($this->lockFile) && (time() - @filemtime($this->lockFile)) > 60 * 10) { 922becc3acSgerardnico if (!@unlink($this->lockFile)) { 93a6d63b89Sgerardnico throw new ExceptionRuntimeInternal("Removing the lock failed ($this->lockFile)"); 94a6d63b89Sgerardnico } 95a6d63b89Sgerardnico } 969fc13f00Sgerardnico $run++; 979fc13f00Sgerardnico if ($run >= $this->timeOut) { 989fc13f00Sgerardnico throw new ExceptionTimeOut("Unable to get the lock ($this->lockFile) for ($this->timeOut) seconds"); 99a6d63b89Sgerardnico } 100a6d63b89Sgerardnico } 101a6d63b89Sgerardnico if ($this->perm) { 102a6d63b89Sgerardnico chmod($this->lockFile, $this->perm); 103a6d63b89Sgerardnico } 104*ea72c629Sgerardnico register_shutdown_function([Lock::class, 'shutdownHandling'], $this->lockName); 1059fc13f00Sgerardnico return $this; 106a6d63b89Sgerardnico 107a6d63b89Sgerardnico } 108a6d63b89Sgerardnico 109a6d63b89Sgerardnico /** 110*ea72c629Sgerardnico * 111*ea72c629Sgerardnico * A function that is called when the process shutdown 112*ea72c629Sgerardnico * due to time exceed for instance that cleans the lock created. 113*ea72c629Sgerardnico * 114*ea72c629Sgerardnico * https://www.php.net/manual/en/function.register-shutdown-function.php 115*ea72c629Sgerardnico * 116*ea72c629Sgerardnico * Why ? 117*ea72c629Sgerardnico * The lock are created in the `before` of the the task runner event 118*ea72c629Sgerardnico * and deleted in the `after` of the task runner event 119*ea72c629Sgerardnico * If their is an error somewhere such as as a timeout, the lock 120*ea72c629Sgerardnico * is not deleted and there is no task runner anymore for 5 minutes. 121*ea72c629Sgerardnico * 122*ea72c629Sgerardnico * @param $name - the lock name 123*ea72c629Sgerardnico * @return void 124*ea72c629Sgerardnico */ 125*ea72c629Sgerardnico public static function shutdownHandling($name) 126*ea72c629Sgerardnico { 127*ea72c629Sgerardnico print "Lock::shutdownHandling(): Deleting the lock $name" . NL; 128*ea72c629Sgerardnico Lock::create($name)->release(); 129*ea72c629Sgerardnico } 130*ea72c629Sgerardnico 131*ea72c629Sgerardnico /** 1322becc3acSgerardnico * Release the lock 1332becc3acSgerardnico * and the resources 1342becc3acSgerardnico * (Need to be called in all cases) 135a6d63b89Sgerardnico */ 136a6d63b89Sgerardnico function release() 137a6d63b89Sgerardnico { 1382becc3acSgerardnico if ($this->filePointer !== null) { 1392becc3acSgerardnico fclose($this->filePointer); 1402becc3acSgerardnico $this->filePointer = null; 1412becc3acSgerardnico } 1422becc3acSgerardnico if (file_exists($this->lockFile)) { 1432becc3acSgerardnico unlink($this->lockFile); 1442becc3acSgerardnico } 145a6d63b89Sgerardnico } 146a6d63b89Sgerardnico 1470360a848Sgerardnico public function isReleased(): bool 1480360a848Sgerardnico { 1492becc3acSgerardnico return !file_exists($this->lockFile); 1500360a848Sgerardnico } 1510360a848Sgerardnico 1522becc3acSgerardnico public function isLocked(): bool 1532becc3acSgerardnico { 1542becc3acSgerardnico return file_exists($this->lockFile); 1552becc3acSgerardnico } 1562becc3acSgerardnico 1572becc3acSgerardnico public function setTimeout(int $int): Lock 1589fc13f00Sgerardnico { 1599fc13f00Sgerardnico $this->timeOut = $int; 1609fc13f00Sgerardnico return $this; 1619fc13f00Sgerardnico } 1629fc13f00Sgerardnico 1632becc3acSgerardnico private function getLock(): bool 1642becc3acSgerardnico { 1652becc3acSgerardnico /** 1662becc3acSgerardnico * We test also on the file because 1672becc3acSgerardnico * on some operating systems, flock() is implemented at the process level. 1682becc3acSgerardnico * 1692becc3acSgerardnico * ie when using a multithreaded server API you may not be able to rely on flock() 1702becc3acSgerardnico * to protect files against other PHP scripts running in parallel threads of the same server instance 1712becc3acSgerardnico */ 1722becc3acSgerardnico if (file_exists($this->lockFile)) { 1732becc3acSgerardnico return false; 1742becc3acSgerardnico } 1752becc3acSgerardnico 1762becc3acSgerardnico if ($this->filePointer === null) { 1772becc3acSgerardnico $mode = "c"; // as specified in the doc 1782becc3acSgerardnico $this->filePointer = fopen($this->lockFile, $mode); 1792becc3acSgerardnico } 1802becc3acSgerardnico /** 1812becc3acSgerardnico * LOCK_EX: exclusive lock 1822becc3acSgerardnico * LOCK_NB: to not wait 1832becc3acSgerardnico */ 1842becc3acSgerardnico return flock($this->filePointer, LOCK_EX | LOCK_NB); 1852becc3acSgerardnico } 1862becc3acSgerardnico 187a6d63b89Sgerardnico} 188