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