1a6d63b89Sgerardnico<?php 2a6d63b89Sgerardnico 3a6d63b89Sgerardniconamespace ComboStrap; 4a6d63b89Sgerardnico 5a6d63b89Sgerardnico 6a6d63b89Sgerardnicouse dokuwiki\Search\Indexer; 7ad79af66SNicouse 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; 43*4d15adccSNico /** 44*4d15adccSNico * @var int 1 - no timeout just returns 45*4d15adccSNico */ 46*4d15adccSNico private int $timeOut = 1; 472becc3acSgerardnico /** 482becc3acSgerardnico * @var false|resource 492becc3acSgerardnico */ 502becc3acSgerardnico private $filePointer = null; 51a6d63b89Sgerardnico 52a6d63b89Sgerardnico 53a6d63b89Sgerardnico /** 54a6d63b89Sgerardnico * @param string $name 55a6d63b89Sgerardnico */ 56a6d63b89Sgerardnico public function __construct(string $name) 57a6d63b89Sgerardnico { 58a6d63b89Sgerardnico $this->lockName = $name; 59a6d63b89Sgerardnico global $conf; 60a6d63b89Sgerardnico $this->lockFile = $conf['lockdir'] . "/_{$this->lockName}.lock"; 61a6d63b89Sgerardnico $this->perm = $conf['dperm'] ?? null; 62a6d63b89Sgerardnico } 63a6d63b89Sgerardnico 64a6d63b89Sgerardnico public static function create(string $name): Lock 65a6d63b89Sgerardnico { 66a6d63b89Sgerardnico return new Lock($name); 67a6d63b89Sgerardnico } 68a6d63b89Sgerardnico 69a6d63b89Sgerardnico /** 70a6d63b89Sgerardnico * @throws ExceptionTimeOut - with the timeout 71a6d63b89Sgerardnico */ 729fc13f00Sgerardnico function acquire(): Lock 73a6d63b89Sgerardnico { 74a6d63b89Sgerardnico $run = 0; 752becc3acSgerardnico /** 762becc3acSgerardnico * The flock function follows the semantics of the Unix system call bearing the same name. 772becc3acSgerardnico * Flock utilizes ADVISORY locking only; that is: 782becc3acSgerardnico * * other processes may ignore the lock completely it only affects those that call the flock call. 792becc3acSgerardnico * 802becc3acSgerardnico * * LOCK_SH means SHARED LOCK. Any number of processes MAY HAVE A SHARED LOCK simultaneously. It is commonly called a reader lock. 812becc3acSgerardnico * * LOCK_EX means EXCLUSIVE LOCK. Only a single process may possess an exclusive lock to a given file at a time. 822becc3acSgerardnico * 832becc3acSgerardnico * ie if the file has been LOCKED with LOCK_SH in another process, 842becc3acSgerardnico * * flock with LOCK_SH will SUCCEED. 852becc3acSgerardnico * * flock with LOCK_EX will BLOCK UNTIL ALL READER LOCKS HAVE BEEN RELEASED. 862becc3acSgerardnico * 872becc3acSgerardnico * When the file is closed, the lock is released by the system anyway. 882becc3acSgerardnico */ 892becc3acSgerardnico // LOCK_NB to not block the process 902becc3acSgerardnico while (!$this->getLock()) { 91a6d63b89Sgerardnico /** 922becc3acSgerardnico * Old lock ? More than 10 minutes run 93a6d63b89Sgerardnico */ 942becc3acSgerardnico if (is_file($this->lockFile) && (time() - @filemtime($this->lockFile)) > 60 * 10) { 952becc3acSgerardnico if (!@unlink($this->lockFile)) { 96a6d63b89Sgerardnico throw new ExceptionRuntimeInternal("Removing the lock failed ($this->lockFile)"); 97a6d63b89Sgerardnico } 98a6d63b89Sgerardnico } 999fc13f00Sgerardnico $run++; 1009fc13f00Sgerardnico if ($run >= $this->timeOut) { 1019fc13f00Sgerardnico throw new ExceptionTimeOut("Unable to get the lock ($this->lockFile) for ($this->timeOut) seconds"); 102a6d63b89Sgerardnico } 103*4d15adccSNico sleep(1); 104a6d63b89Sgerardnico } 105a6d63b89Sgerardnico if ($this->perm) { 106a6d63b89Sgerardnico chmod($this->lockFile, $this->perm); 107a6d63b89Sgerardnico } 108ea72c629Sgerardnico register_shutdown_function([Lock::class, 'shutdownHandling'], $this->lockName); 1099fc13f00Sgerardnico return $this; 110a6d63b89Sgerardnico 111a6d63b89Sgerardnico } 112a6d63b89Sgerardnico 113a6d63b89Sgerardnico /** 114ea72c629Sgerardnico * 115ea72c629Sgerardnico * A function that is called when the process shutdown 116ea72c629Sgerardnico * due to time exceed for instance that cleans the lock created. 117ea72c629Sgerardnico * 118ea72c629Sgerardnico * https://www.php.net/manual/en/function.register-shutdown-function.php 119ea72c629Sgerardnico * 120ea72c629Sgerardnico * Why ? 121ea72c629Sgerardnico * The lock are created in the `before` of the the task runner event 122ea72c629Sgerardnico * and deleted in the `after` of the task runner event 123ea72c629Sgerardnico * If their is an error somewhere such as as a timeout, the lock 124ea72c629Sgerardnico * is not deleted and there is no task runner anymore for 5 minutes. 125ea72c629Sgerardnico * 126ea72c629Sgerardnico * @param $name - the lock name 127ea72c629Sgerardnico * @return void 128ea72c629Sgerardnico */ 129ea72c629Sgerardnico public static function shutdownHandling($name) 130ea72c629Sgerardnico { 131ad79af66SNico /** 132ad79af66SNico * For an unknown reason, if we print in this function 133ad79af66SNico * that is a called via the register_shutdown_function of {@link Lock::acquire()} 134ad79af66SNico * no content is send with the {@link TaskRunner} (ie the gif is not sent) 135ad79af66SNico */ 136ad79af66SNico global $INPUT, $conf; 137ad79af66SNico $output = $INPUT->has('debug') && $conf['allowdebug']; 138ad79af66SNico if ($output) { 139ad79af66SNico print "Lock::shutdownHandling(): Deleting the lock $name"; 140ad79af66SNico } 141ad79af66SNico 142ea72c629Sgerardnico Lock::create($name)->release(); 143ea72c629Sgerardnico } 144ea72c629Sgerardnico 145ea72c629Sgerardnico /** 1462becc3acSgerardnico * Release the lock 1472becc3acSgerardnico * and the resources 1482becc3acSgerardnico * (Need to be called in all cases) 149a6d63b89Sgerardnico */ 150a6d63b89Sgerardnico function release() 151a6d63b89Sgerardnico { 1522becc3acSgerardnico if ($this->filePointer !== null) { 1532becc3acSgerardnico fclose($this->filePointer); 1542becc3acSgerardnico $this->filePointer = null; 1552becc3acSgerardnico } 1562becc3acSgerardnico if (file_exists($this->lockFile)) { 1572becc3acSgerardnico unlink($this->lockFile); 1582becc3acSgerardnico } 159a6d63b89Sgerardnico } 160a6d63b89Sgerardnico 1610360a848Sgerardnico public function isReleased(): bool 1620360a848Sgerardnico { 1632becc3acSgerardnico return !file_exists($this->lockFile); 1640360a848Sgerardnico } 1650360a848Sgerardnico 1662becc3acSgerardnico public function isLocked(): bool 1672becc3acSgerardnico { 1682becc3acSgerardnico return file_exists($this->lockFile); 1692becc3acSgerardnico } 1702becc3acSgerardnico 1712becc3acSgerardnico public function setTimeout(int $int): Lock 1729fc13f00Sgerardnico { 1739fc13f00Sgerardnico $this->timeOut = $int; 1749fc13f00Sgerardnico return $this; 1759fc13f00Sgerardnico } 1769fc13f00Sgerardnico 1772becc3acSgerardnico private function getLock(): bool 1782becc3acSgerardnico { 1792becc3acSgerardnico /** 1802becc3acSgerardnico * We test also on the file because 1812becc3acSgerardnico * on some operating systems, flock() is implemented at the process level. 1822becc3acSgerardnico * 1832becc3acSgerardnico * ie when using a multithreaded server API you may not be able to rely on flock() 1842becc3acSgerardnico * to protect files against other PHP scripts running in parallel threads of the same server instance 1852becc3acSgerardnico */ 1862becc3acSgerardnico if (file_exists($this->lockFile)) { 1872becc3acSgerardnico return false; 1882becc3acSgerardnico } 1892becc3acSgerardnico 1902becc3acSgerardnico if ($this->filePointer === null) { 1912becc3acSgerardnico $mode = "c"; // as specified in the doc 1922becc3acSgerardnico $this->filePointer = fopen($this->lockFile, $mode); 1932becc3acSgerardnico } 1942becc3acSgerardnico /** 1952becc3acSgerardnico * LOCK_EX: exclusive lock 1962becc3acSgerardnico * LOCK_NB: to not wait 1972becc3acSgerardnico */ 1982becc3acSgerardnico return flock($this->filePointer, LOCK_EX | LOCK_NB); 1992becc3acSgerardnico } 2002becc3acSgerardnico 201a6d63b89Sgerardnico} 202