1a6d63b89Sgerardnico<?php 2a6d63b89Sgerardnico 3a6d63b89Sgerardniconamespace ComboStrap; 4a6d63b89Sgerardnico 5a6d63b89Sgerardnico 6a6d63b89Sgerardnicouse dokuwiki\Search\Indexer; 7*ad79af66SNicouse dokuwiki\TaskRunner; 8a6d63b89Sgerardnico 9a6d63b89Sgerardnico/** 10a6d63b89Sgerardnico * Adapted from the {@link Indexer::lock()} 11a6d63b89Sgerardnico * because the TaskRunner does not run serially 12a6d63b89Sgerardnico * Only the indexer does 13a6d63b89Sgerardnico * https://forum.dokuwiki.org/d/21044-taskrunner-running-multiple-times-eating-the-memory-lock 14ea72c629Sgerardnico * 15ea72c629Sgerardnico * Example with debug 16ea72c629Sgerardnico * ``` 17ea72c629Sgerardnico * ComboLockTaskRunner(): Trying to get a lock 18ea72c629Sgerardnico * ComboLockTaskRunner(): Locked 19ea72c629Sgerardnico * runIndexer(): started 20ea72c629Sgerardnico * Indexer: index for web:browser:selection up to date 21ea72c629Sgerardnico * runSitemapper(): started 22ea72c629Sgerardnico * runSitemapper(): finished 23ea72c629Sgerardnico * sendDigest(): started 24ea72c629Sgerardnico * sendDigest(): disabled 25ea72c629Sgerardnico * runTrimRecentChanges(): started 26ea72c629Sgerardnico * runTrimRecentChanges(): finished 27ea72c629Sgerardnico * runTrimRecentChanges(1): started 28ea72c629Sgerardnico * runTrimRecentChanges(1): finished 29ea72c629Sgerardnico * ComboDispatchEvent(): Trying to get a lock 30ea72c629Sgerardnico * ComboDispatchEvent(): Locked 31ea72c629Sgerardnico * ComboDispatchEvent(): Lock Released 32ea72c629Sgerardnico * ComboLockTaskRunner(): Lock Released 33ea72c629Sgerardnico * 34a6d63b89Sgerardnico */ 35a6d63b89Sgerardnicoclass Lock 36a6d63b89Sgerardnico{ 37a6d63b89Sgerardnico private string $lockName; 38a6d63b89Sgerardnico private string $lockFile; 39a6d63b89Sgerardnico /** 40a6d63b89Sgerardnico * @var mixed|null 41a6d63b89Sgerardnico */ 42a6d63b89Sgerardnico private $perm; 439fc13f00Sgerardnico private int $timeOut = 5; 442becc3acSgerardnico /** 452becc3acSgerardnico * @var false|resource 462becc3acSgerardnico */ 472becc3acSgerardnico private $filePointer = null; 48a6d63b89Sgerardnico 49a6d63b89Sgerardnico 50a6d63b89Sgerardnico /** 51a6d63b89Sgerardnico * @param string $name 52a6d63b89Sgerardnico */ 53a6d63b89Sgerardnico public function __construct(string $name) 54a6d63b89Sgerardnico { 55a6d63b89Sgerardnico $this->lockName = $name; 56a6d63b89Sgerardnico global $conf; 57a6d63b89Sgerardnico $this->lockFile = $conf['lockdir'] . "/_{$this->lockName}.lock"; 58a6d63b89Sgerardnico $this->perm = $conf['dperm'] ?? null; 59a6d63b89Sgerardnico } 60a6d63b89Sgerardnico 61a6d63b89Sgerardnico public static function create(string $name): Lock 62a6d63b89Sgerardnico { 63a6d63b89Sgerardnico return new Lock($name); 64a6d63b89Sgerardnico } 65a6d63b89Sgerardnico 66a6d63b89Sgerardnico /** 67a6d63b89Sgerardnico * @throws ExceptionTimeOut - with the timeout 68a6d63b89Sgerardnico */ 699fc13f00Sgerardnico function acquire(): Lock 70a6d63b89Sgerardnico { 71a6d63b89Sgerardnico $run = 0; 722becc3acSgerardnico /** 732becc3acSgerardnico * The flock function follows the semantics of the Unix system call bearing the same name. 742becc3acSgerardnico * Flock utilizes ADVISORY locking only; that is: 752becc3acSgerardnico * * other processes may ignore the lock completely it only affects those that call the flock call. 762becc3acSgerardnico * 772becc3acSgerardnico * * LOCK_SH means SHARED LOCK. Any number of processes MAY HAVE A SHARED LOCK simultaneously. It is commonly called a reader lock. 782becc3acSgerardnico * * LOCK_EX means EXCLUSIVE LOCK. Only a single process may possess an exclusive lock to a given file at a time. 792becc3acSgerardnico * 802becc3acSgerardnico * ie if the file has been LOCKED with LOCK_SH in another process, 812becc3acSgerardnico * * flock with LOCK_SH will SUCCEED. 822becc3acSgerardnico * * flock with LOCK_EX will BLOCK UNTIL ALL READER LOCKS HAVE BEEN RELEASED. 832becc3acSgerardnico * 842becc3acSgerardnico * When the file is closed, the lock is released by the system anyway. 852becc3acSgerardnico */ 862becc3acSgerardnico // LOCK_NB to not block the process 872becc3acSgerardnico while (!$this->getLock()) { 88ea72c629Sgerardnico sleep(1); 89a6d63b89Sgerardnico /** 902becc3acSgerardnico * Old lock ? More than 10 minutes run 91a6d63b89Sgerardnico */ 922becc3acSgerardnico if (is_file($this->lockFile) && (time() - @filemtime($this->lockFile)) > 60 * 10) { 932becc3acSgerardnico if (!@unlink($this->lockFile)) { 94a6d63b89Sgerardnico throw new ExceptionRuntimeInternal("Removing the lock failed ($this->lockFile)"); 95a6d63b89Sgerardnico } 96a6d63b89Sgerardnico } 979fc13f00Sgerardnico $run++; 989fc13f00Sgerardnico if ($run >= $this->timeOut) { 999fc13f00Sgerardnico throw new ExceptionTimeOut("Unable to get the lock ($this->lockFile) for ($this->timeOut) seconds"); 100a6d63b89Sgerardnico } 101a6d63b89Sgerardnico } 102a6d63b89Sgerardnico if ($this->perm) { 103a6d63b89Sgerardnico chmod($this->lockFile, $this->perm); 104a6d63b89Sgerardnico } 105ea72c629Sgerardnico register_shutdown_function([Lock::class, 'shutdownHandling'], $this->lockName); 1069fc13f00Sgerardnico return $this; 107a6d63b89Sgerardnico 108a6d63b89Sgerardnico } 109a6d63b89Sgerardnico 110a6d63b89Sgerardnico /** 111ea72c629Sgerardnico * 112ea72c629Sgerardnico * A function that is called when the process shutdown 113ea72c629Sgerardnico * due to time exceed for instance that cleans the lock created. 114ea72c629Sgerardnico * 115ea72c629Sgerardnico * https://www.php.net/manual/en/function.register-shutdown-function.php 116ea72c629Sgerardnico * 117ea72c629Sgerardnico * Why ? 118ea72c629Sgerardnico * The lock are created in the `before` of the the task runner event 119ea72c629Sgerardnico * and deleted in the `after` of the task runner event 120ea72c629Sgerardnico * If their is an error somewhere such as as a timeout, the lock 121ea72c629Sgerardnico * is not deleted and there is no task runner anymore for 5 minutes. 122ea72c629Sgerardnico * 123ea72c629Sgerardnico * @param $name - the lock name 124ea72c629Sgerardnico * @return void 125ea72c629Sgerardnico */ 126ea72c629Sgerardnico public static function shutdownHandling($name) 127ea72c629Sgerardnico { 128*ad79af66SNico /** 129*ad79af66SNico * For an unknown reason, if we print in this function 130*ad79af66SNico * that is a called via the register_shutdown_function of {@link Lock::acquire()} 131*ad79af66SNico * no content is send with the {@link TaskRunner} (ie the gif is not sent) 132*ad79af66SNico */ 133*ad79af66SNico global $INPUT, $conf; 134*ad79af66SNico $output = $INPUT->has('debug') && $conf['allowdebug']; 135*ad79af66SNico if ($output) { 136*ad79af66SNico print "Lock::shutdownHandling(): Deleting the lock $name"; 137*ad79af66SNico } 138*ad79af66SNico 139ea72c629Sgerardnico Lock::create($name)->release(); 140ea72c629Sgerardnico } 141ea72c629Sgerardnico 142ea72c629Sgerardnico /** 1432becc3acSgerardnico * Release the lock 1442becc3acSgerardnico * and the resources 1452becc3acSgerardnico * (Need to be called in all cases) 146a6d63b89Sgerardnico */ 147a6d63b89Sgerardnico function release() 148a6d63b89Sgerardnico { 1492becc3acSgerardnico if ($this->filePointer !== null) { 1502becc3acSgerardnico fclose($this->filePointer); 1512becc3acSgerardnico $this->filePointer = null; 1522becc3acSgerardnico } 1532becc3acSgerardnico if (file_exists($this->lockFile)) { 1542becc3acSgerardnico unlink($this->lockFile); 1552becc3acSgerardnico } 156a6d63b89Sgerardnico } 157a6d63b89Sgerardnico 1580360a848Sgerardnico public function isReleased(): bool 1590360a848Sgerardnico { 1602becc3acSgerardnico return !file_exists($this->lockFile); 1610360a848Sgerardnico } 1620360a848Sgerardnico 1632becc3acSgerardnico public function isLocked(): bool 1642becc3acSgerardnico { 1652becc3acSgerardnico return file_exists($this->lockFile); 1662becc3acSgerardnico } 1672becc3acSgerardnico 1682becc3acSgerardnico public function setTimeout(int $int): Lock 1699fc13f00Sgerardnico { 1709fc13f00Sgerardnico $this->timeOut = $int; 1719fc13f00Sgerardnico return $this; 1729fc13f00Sgerardnico } 1739fc13f00Sgerardnico 1742becc3acSgerardnico private function getLock(): bool 1752becc3acSgerardnico { 1762becc3acSgerardnico /** 1772becc3acSgerardnico * We test also on the file because 1782becc3acSgerardnico * on some operating systems, flock() is implemented at the process level. 1792becc3acSgerardnico * 1802becc3acSgerardnico * ie when using a multithreaded server API you may not be able to rely on flock() 1812becc3acSgerardnico * to protect files against other PHP scripts running in parallel threads of the same server instance 1822becc3acSgerardnico */ 1832becc3acSgerardnico if (file_exists($this->lockFile)) { 1842becc3acSgerardnico return false; 1852becc3acSgerardnico } 1862becc3acSgerardnico 1872becc3acSgerardnico if ($this->filePointer === null) { 1882becc3acSgerardnico $mode = "c"; // as specified in the doc 1892becc3acSgerardnico $this->filePointer = fopen($this->lockFile, $mode); 1902becc3acSgerardnico } 1912becc3acSgerardnico /** 1922becc3acSgerardnico * LOCK_EX: exclusive lock 1932becc3acSgerardnico * LOCK_NB: to not wait 1942becc3acSgerardnico */ 1952becc3acSgerardnico return flock($this->filePointer, LOCK_EX | LOCK_NB); 1962becc3acSgerardnico } 1972becc3acSgerardnico 198a6d63b89Sgerardnico} 199