1<?php 2 3namespace ComboStrap; 4 5 6use dokuwiki\Search\Indexer; 7 8/** 9 * Adapted from the {@link Indexer::lock()} 10 * because the TaskRunner does not run serially 11 * Only the indexer does 12 * https://forum.dokuwiki.org/d/21044-taskrunner-running-multiple-times-eating-the-memory-lock 13 */ 14class Lock 15{ 16 private string $lockName; 17 private string $lockFile; 18 /** 19 * @var mixed|null 20 */ 21 private $perm; 22 private int $timeOut = 5; 23 /** 24 * @var false|resource 25 */ 26 private $filePointer = null; 27 28 29 /** 30 * @param string $name 31 */ 32 public function __construct(string $name) 33 { 34 $this->lockName = $name; 35 global $conf; 36 $this->lockFile = $conf['lockdir'] . "/_{$this->lockName}.lock"; 37 $this->perm = $conf['dperm'] ?? null; 38 } 39 40 public static function create(string $name): Lock 41 { 42 return new Lock($name); 43 } 44 45 /** 46 * @throws ExceptionTimeOut - with the timeout 47 */ 48 function acquire(): Lock 49 { 50 $run = 0; 51 /** 52 * The flock function follows the semantics of the Unix system call bearing the same name. 53 * Flock utilizes ADVISORY locking only; that is: 54 * * other processes may ignore the lock completely it only affects those that call the flock call. 55 * 56 * * LOCK_SH means SHARED LOCK. Any number of processes MAY HAVE A SHARED LOCK simultaneously. It is commonly called a reader lock. 57 * * LOCK_EX means EXCLUSIVE LOCK. Only a single process may possess an exclusive lock to a given file at a time. 58 * 59 * ie if the file has been LOCKED with LOCK_SH in another process, 60 * * flock with LOCK_SH will SUCCEED. 61 * * flock with LOCK_EX will BLOCK UNTIL ALL READER LOCKS HAVE BEEN RELEASED. 62 * 63 * When the file is closed, the lock is released by the system anyway. 64 */ 65 // LOCK_NB to not block the process 66 while (!$this->getLock()) { 67 usleep(1000); 68 /** 69 * Old lock ? More than 10 minutes run 70 */ 71 if (is_file($this->lockFile) && (time() - @filemtime($this->lockFile)) > 60 * 10) { 72 if (!@unlink($this->lockFile)) { 73 throw new ExceptionRuntimeInternal("Removing the lock failed ($this->lockFile)"); 74 } 75 } 76 $run++; 77 if ($run >= $this->timeOut) { 78 throw new ExceptionTimeOut("Unable to get the lock ($this->lockFile) for ($this->timeOut) seconds"); 79 } 80 } 81 if ($this->perm) { 82 chmod($this->lockFile, $this->perm); 83 } 84 return $this; 85 86 } 87 88 /** 89 * Release the lock 90 * and the resources 91 * (Need to be called in all cases) 92 */ 93 function release() 94 { 95 if ($this->filePointer !== null) { 96 fclose($this->filePointer); 97 $this->filePointer = null; 98 } 99 if (file_exists($this->lockFile)) { 100 unlink($this->lockFile); 101 } 102 } 103 104 public function isReleased(): bool 105 { 106 return !file_exists($this->lockFile); 107 } 108 109 public function isLocked(): bool 110 { 111 return file_exists($this->lockFile); 112 } 113 114 public function setTimeout(int $int): Lock 115 { 116 $this->timeOut = $int; 117 return $this; 118 } 119 120 private function getLock(): bool 121 { 122 /** 123 * We test also on the file because 124 * on some operating systems, flock() is implemented at the process level. 125 * 126 * ie when using a multithreaded server API you may not be able to rely on flock() 127 * to protect files against other PHP scripts running in parallel threads of the same server instance 128 */ 129 if (file_exists($this->lockFile)) { 130 return false; 131 } 132 133 if ($this->filePointer === null) { 134 $mode = "c"; // as specified in the doc 135 $this->filePointer = fopen($this->lockFile, $mode); 136 } 137 /** 138 * LOCK_EX: exclusive lock 139 * LOCK_NB: to not wait 140 */ 141 return flock($this->filePointer, LOCK_EX | LOCK_NB); 142 } 143 144} 145