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