xref: /dokuwiki/inc/Search/Index/Lock.php (revision 21fbd01b3c3eea88b767376b7b158f31f0f63127)
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