xref: /template/strap/ComboStrap/Lock.php (revision 4d15adcc14acacfdcef0f5f356b5ad038e9e1da9)
1a6d63b89Sgerardnico<?php
2a6d63b89Sgerardnico
3a6d63b89Sgerardniconamespace ComboStrap;
4a6d63b89Sgerardnico
5a6d63b89Sgerardnico
6a6d63b89Sgerardnicouse dokuwiki\Search\Indexer;
7ad79af66SNicouse dokuwiki\TaskRunner;
8a6d63b89Sgerardnico
9a6d63b89Sgerardnico/**
10a6d63b89Sgerardnico * Adapted from the {@link Indexer::lock()}
11a6d63b89Sgerardnico * because the TaskRunner does not run serially
12a6d63b89Sgerardnico * Only the indexer does
13a6d63b89Sgerardnico * https://forum.dokuwiki.org/d/21044-taskrunner-running-multiple-times-eating-the-memory-lock
14ea72c629Sgerardnico *
15ea72c629Sgerardnico * Example with debug
16ea72c629Sgerardnico * ```
17ea72c629Sgerardnico * ComboLockTaskRunner(): Trying to get a lock
18ea72c629Sgerardnico * ComboLockTaskRunner(): Locked
19ea72c629Sgerardnico * runIndexer(): started
20ea72c629Sgerardnico * Indexer: index for web:browser:selection up to date
21ea72c629Sgerardnico * runSitemapper(): started
22ea72c629Sgerardnico * runSitemapper(): finished
23ea72c629Sgerardnico * sendDigest(): started
24ea72c629Sgerardnico * sendDigest(): disabled
25ea72c629Sgerardnico * runTrimRecentChanges(): started
26ea72c629Sgerardnico * runTrimRecentChanges(): finished
27ea72c629Sgerardnico * runTrimRecentChanges(1): started
28ea72c629Sgerardnico * runTrimRecentChanges(1): finished
29ea72c629Sgerardnico * ComboDispatchEvent(): Trying to get a lock
30ea72c629Sgerardnico * ComboDispatchEvent(): Locked
31ea72c629Sgerardnico * ComboDispatchEvent(): Lock Released
32ea72c629Sgerardnico * ComboLockTaskRunner(): Lock Released
33ea72c629Sgerardnico *
34a6d63b89Sgerardnico */
35a6d63b89Sgerardnicoclass Lock
36a6d63b89Sgerardnico{
37a6d63b89Sgerardnico    private string $lockName;
38a6d63b89Sgerardnico    private string $lockFile;
39a6d63b89Sgerardnico    /**
40a6d63b89Sgerardnico     * @var mixed|null
41a6d63b89Sgerardnico     */
42a6d63b89Sgerardnico    private $perm;
43*4d15adccSNico    /**
44*4d15adccSNico     * @var int 1 - no timeout just returns
45*4d15adccSNico     */
46*4d15adccSNico    private int $timeOut = 1;
472becc3acSgerardnico    /**
482becc3acSgerardnico     * @var false|resource
492becc3acSgerardnico     */
502becc3acSgerardnico    private $filePointer = null;
51a6d63b89Sgerardnico
52a6d63b89Sgerardnico
53a6d63b89Sgerardnico    /**
54a6d63b89Sgerardnico     * @param string $name
55a6d63b89Sgerardnico     */
56a6d63b89Sgerardnico    public function __construct(string $name)
57a6d63b89Sgerardnico    {
58a6d63b89Sgerardnico        $this->lockName = $name;
59a6d63b89Sgerardnico        global $conf;
60a6d63b89Sgerardnico        $this->lockFile = $conf['lockdir'] . "/_{$this->lockName}.lock";
61a6d63b89Sgerardnico        $this->perm = $conf['dperm'] ?? null;
62a6d63b89Sgerardnico    }
63a6d63b89Sgerardnico
64a6d63b89Sgerardnico    public static function create(string $name): Lock
65a6d63b89Sgerardnico    {
66a6d63b89Sgerardnico        return new Lock($name);
67a6d63b89Sgerardnico    }
68a6d63b89Sgerardnico
69a6d63b89Sgerardnico    /**
70a6d63b89Sgerardnico     * @throws ExceptionTimeOut - with the timeout
71a6d63b89Sgerardnico     */
729fc13f00Sgerardnico    function acquire(): Lock
73a6d63b89Sgerardnico    {
74a6d63b89Sgerardnico        $run = 0;
752becc3acSgerardnico        /**
762becc3acSgerardnico         * The flock function follows the semantics of the Unix system call bearing the same name.
772becc3acSgerardnico         * Flock utilizes ADVISORY locking only; that is:
782becc3acSgerardnico         * * other processes may ignore the lock completely it only affects those that call the flock call.
792becc3acSgerardnico         *
802becc3acSgerardnico         * * LOCK_SH means SHARED LOCK. Any number of processes MAY HAVE A SHARED LOCK simultaneously. It is commonly called a reader lock.
812becc3acSgerardnico         * * LOCK_EX means EXCLUSIVE LOCK. Only a single process may possess an exclusive lock to a given file at a time.
822becc3acSgerardnico         *
832becc3acSgerardnico         * ie if the file has been LOCKED with LOCK_SH in another process,
842becc3acSgerardnico         * * flock with LOCK_SH will SUCCEED.
852becc3acSgerardnico         * * flock with LOCK_EX will BLOCK UNTIL ALL READER LOCKS HAVE BEEN RELEASED.
862becc3acSgerardnico         *
872becc3acSgerardnico         * When the file is closed, the lock is released by the system anyway.
882becc3acSgerardnico         */
892becc3acSgerardnico        // LOCK_NB to not block the process
902becc3acSgerardnico        while (!$this->getLock()) {
91a6d63b89Sgerardnico            /**
922becc3acSgerardnico             * Old lock ? More than 10 minutes run
93a6d63b89Sgerardnico             */
942becc3acSgerardnico            if (is_file($this->lockFile) && (time() - @filemtime($this->lockFile)) > 60 * 10) {
952becc3acSgerardnico                if (!@unlink($this->lockFile)) {
96a6d63b89Sgerardnico                    throw new ExceptionRuntimeInternal("Removing the lock failed ($this->lockFile)");
97a6d63b89Sgerardnico                }
98a6d63b89Sgerardnico            }
999fc13f00Sgerardnico            $run++;
1009fc13f00Sgerardnico            if ($run >= $this->timeOut) {
1019fc13f00Sgerardnico                throw new ExceptionTimeOut("Unable to get the lock ($this->lockFile) for ($this->timeOut) seconds");
102a6d63b89Sgerardnico            }
103*4d15adccSNico            sleep(1);
104a6d63b89Sgerardnico        }
105a6d63b89Sgerardnico        if ($this->perm) {
106a6d63b89Sgerardnico            chmod($this->lockFile, $this->perm);
107a6d63b89Sgerardnico        }
108ea72c629Sgerardnico        register_shutdown_function([Lock::class, 'shutdownHandling'], $this->lockName);
1099fc13f00Sgerardnico        return $this;
110a6d63b89Sgerardnico
111a6d63b89Sgerardnico    }
112a6d63b89Sgerardnico
113a6d63b89Sgerardnico    /**
114ea72c629Sgerardnico     *
115ea72c629Sgerardnico     * A function that is called when the process shutdown
116ea72c629Sgerardnico     * due to time exceed for instance that cleans the lock created.
117ea72c629Sgerardnico     *
118ea72c629Sgerardnico     * https://www.php.net/manual/en/function.register-shutdown-function.php
119ea72c629Sgerardnico     *
120ea72c629Sgerardnico     * Why ?
121ea72c629Sgerardnico     * The lock are created in the `before` of the the task runner event
122ea72c629Sgerardnico     * and deleted in the `after` of the task runner event
123ea72c629Sgerardnico     * If their is an error somewhere such as as a timeout, the lock
124ea72c629Sgerardnico     * is not deleted and there is no task runner anymore for 5 minutes.
125ea72c629Sgerardnico     *
126ea72c629Sgerardnico     * @param $name - the lock name
127ea72c629Sgerardnico     * @return void
128ea72c629Sgerardnico     */
129ea72c629Sgerardnico    public static function shutdownHandling($name)
130ea72c629Sgerardnico    {
131ad79af66SNico        /**
132ad79af66SNico         * For an unknown reason, if we print in this function
133ad79af66SNico         * that is a called via the register_shutdown_function of {@link Lock::acquire()}
134ad79af66SNico         * no content is send with the {@link TaskRunner} (ie the gif is not sent)
135ad79af66SNico         */
136ad79af66SNico        global $INPUT, $conf;
137ad79af66SNico        $output = $INPUT->has('debug') && $conf['allowdebug'];
138ad79af66SNico        if ($output) {
139ad79af66SNico            print "Lock::shutdownHandling(): Deleting the lock $name";
140ad79af66SNico        }
141ad79af66SNico
142ea72c629Sgerardnico        Lock::create($name)->release();
143ea72c629Sgerardnico    }
144ea72c629Sgerardnico
145ea72c629Sgerardnico    /**
1462becc3acSgerardnico     * Release the lock
1472becc3acSgerardnico     * and the resources
1482becc3acSgerardnico     * (Need to be called in all cases)
149a6d63b89Sgerardnico     */
150a6d63b89Sgerardnico    function release()
151a6d63b89Sgerardnico    {
1522becc3acSgerardnico        if ($this->filePointer !== null) {
1532becc3acSgerardnico            fclose($this->filePointer);
1542becc3acSgerardnico            $this->filePointer = null;
1552becc3acSgerardnico        }
1562becc3acSgerardnico        if (file_exists($this->lockFile)) {
1572becc3acSgerardnico            unlink($this->lockFile);
1582becc3acSgerardnico        }
159a6d63b89Sgerardnico    }
160a6d63b89Sgerardnico
1610360a848Sgerardnico    public function isReleased(): bool
1620360a848Sgerardnico    {
1632becc3acSgerardnico        return !file_exists($this->lockFile);
1640360a848Sgerardnico    }
1650360a848Sgerardnico
1662becc3acSgerardnico    public function isLocked(): bool
1672becc3acSgerardnico    {
1682becc3acSgerardnico        return file_exists($this->lockFile);
1692becc3acSgerardnico    }
1702becc3acSgerardnico
1712becc3acSgerardnico    public function setTimeout(int $int): Lock
1729fc13f00Sgerardnico    {
1739fc13f00Sgerardnico        $this->timeOut = $int;
1749fc13f00Sgerardnico        return $this;
1759fc13f00Sgerardnico    }
1769fc13f00Sgerardnico
1772becc3acSgerardnico    private function getLock(): bool
1782becc3acSgerardnico    {
1792becc3acSgerardnico        /**
1802becc3acSgerardnico         * We test also on the file because
1812becc3acSgerardnico         * on some operating systems, flock() is implemented at the process level.
1822becc3acSgerardnico         *
1832becc3acSgerardnico         * ie when using a multithreaded server API you may not be able to rely on flock()
1842becc3acSgerardnico         * to protect files against other PHP scripts running in parallel threads of the same server instance
1852becc3acSgerardnico         */
1862becc3acSgerardnico        if (file_exists($this->lockFile)) {
1872becc3acSgerardnico            return false;
1882becc3acSgerardnico        }
1892becc3acSgerardnico
1902becc3acSgerardnico        if ($this->filePointer === null) {
1912becc3acSgerardnico            $mode = "c"; // as specified in the doc
1922becc3acSgerardnico            $this->filePointer = fopen($this->lockFile, $mode);
1932becc3acSgerardnico        }
1942becc3acSgerardnico        /**
1952becc3acSgerardnico         * LOCK_EX: exclusive lock
1962becc3acSgerardnico         * LOCK_NB: to not wait
1972becc3acSgerardnico         */
1982becc3acSgerardnico        return flock($this->filePointer, LOCK_EX | LOCK_NB);
1992becc3acSgerardnico    }
2002becc3acSgerardnico
201a6d63b89Sgerardnico}
202