1<?php 2 3namespace dokuwiki\Search\Index; 4 5use dokuwiki\Search\Exception\IndexLockException; 6 7/** 8 * Static lock registry for index writing 9 * 10 * Manages filesystem locks (directories in the lock dir) with in-process 11 * reference counting. Multiple callers can acquire the same lock name — 12 * the filesystem lock is only created on the first acquire and removed 13 * on the last release. 14 */ 15class Lock 16{ 17 /** @var array<string, int> Lock names held by this process with reference counts */ 18 protected static array $held = []; 19 20 /** 21 * Acquire a filesystem lock and register it 22 * 23 * Idempotent within a process — if already held, increments 24 * reference count without touching the filesystem. 25 * 26 * @param string $name The index base name to lock 27 * @throws IndexLockException 28 */ 29 public static function acquire(string $name): void 30 { 31 if (isset(self::$held[$name])) { 32 self::$held[$name]++; 33 return; 34 } 35 36 $dir = self::lockDir($name); 37 if (!@mkdir($dir)) { 38 // check for stale lock 39 if (time() - @filemtime($dir) > 60 * 5) { 40 @rmdir($dir); 41 if (!@mkdir($dir)) { 42 throw new IndexLockException('Could not lock ' . $name); 43 } 44 } else { 45 throw new IndexLockException('Could not lock ' . $name); 46 } 47 } 48 49 self::$held[$name] = 1; 50 } 51 52 /** 53 * Release a filesystem lock 54 * 55 * Decrements reference count. Only removes the filesystem lock 56 * when the count reaches zero. 57 * 58 * @param string $name The index base name to unlock 59 */ 60 public static function release(string $name): void 61 { 62 if (!isset(self::$held[$name])) return; 63 64 self::$held[$name]--; 65 if (self::$held[$name] <= 0) { 66 unset(self::$held[$name]); 67 @rmdir(self::lockDir($name)); 68 } 69 } 70 71 /** 72 * Release all held locks 73 * 74 * Intended for test teardown to ensure a clean state. 75 */ 76 public static function releaseAll(): void 77 { 78 foreach (array_keys(self::$held) as $name) { 79 @rmdir(self::lockDir($name)); 80 } 81 self::$held = []; 82 } 83 84 /** 85 * Get the lock directory path for a given index name 86 * 87 * @param string $name The index base name 88 * @return string 89 */ 90 protected static function lockDir(string $name): string 91 { 92 global $conf; 93 return $conf['lockdir'] . $name . '.index'; 94 } 95} 96