Lines 100.00% 13 / 13
Functions and Methods 100.00% 3 / 3
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
SharedFileShutdown 100.00% 13 / 13 100.00% 3 / 3 7 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 shouldShutdown 100.00% 8 / 8 100.00% 1 / 1 4
 shutdown 100.00% 4 / 4 100.00% 1 / 1 2
1<?php
2
3declare(strict_types=1);
4
5namespace Tomaj\Hermes\Shutdown;
6
7use DateTimeImmutable;
8use DateTimeInterface;
9
10/**
11 * Stops workers through a file. shutdown() writes the shutdown time ("seconds.microseconds")
12 * into the file and sets its modification time to that second. When the file holds no time, or
13 * was touched later (for example with `touch`), its modification time is used instead.
14 */
15final readonly class SharedFileShutdown implements ShutdownInterface
16{
17    public function __construct(
18        private string $filePath,
19    ) {}
20
21    /**
22     * Returns true when the file holds a shutdown time that is not in the future
23     * and is later than the moment this worker started ($startTime).
24     */
25    #[\Override]
26    public function shouldShutdown(DateTimeInterface $startTime): bool
27    {
28        clearstatcache(false, $this->filePath);
29
30        // a missing file means keep running
31        $modified = @filemtime($this->filePath);
32        if ($modified === false) {
33            return false;
34        }
35
36        // The time in the file is only current while the file was not touched since: then it falls
37        // in the second of the modification time. Otherwise (`touch`, or no time in it) use that second.
38        $shutdownTime = ShutdownTime::parse(@file_get_contents($this->filePath));
39        if ($shutdownTime === null || (int) floor($shutdownTime) !== $modified) {
40            $shutdownTime = $modified;
41        }
42
43        return ShutdownTime::isRequested($shutdownTime, $startTime, microtime(true));
44    }
45
46    #[\Override]
47    public function shutdown(?DateTimeInterface $shutdownTime = null): bool
48    {
49        $shutdownTime ??= new DateTimeImmutable();
50
51        // Rewrite the file in place, like `touch` in 5.x: an existing file keeps its owner and mode, and
52        // the directory does not have to be writable. A worker that reads the file during the write sees
53        // an empty or shorter time and at worst stops on its next check instead of this one.
54        if (@file_put_contents($this->filePath, ShutdownTime::format($shutdownTime), LOCK_EX) === false) {
55            return false;
56        }
57
58        return touch($this->filePath, (int) $shutdownTime->format('U'));
59    }
60}