Passed
Push — main ( a747ee...a6a62e )
by Carlos C
01:59 queued 23s
created

SoftDaemon::waitTime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Eclipxe\SoftDaemon;
6
7
use Eclipxe\SoftDaemon\Internal\PcntlSignals;
8
use Eclipxe\SoftDaemon\Sequencers\Fixed as FixedSequencer;
9
10
/**
11
 * @package SoftDaemon
12
 */
13
class SoftDaemon
14
{
15
    /** maximum wait in seconds (1 hour) */
16
    public const DEFAULT_MAXWAIT = 3600;
17
18
    /** minimum wait in seconds (no wait) */
19
    public const DEFAULT_MINWAIT = 0;
20
21
    /** @var Executable **/
22
    protected $executable;
23
24
    /** @var Sequencer **/
25
    protected $sequencer;
26
27
    /** @var int minimal wait */
28
    protected $minwait;
29
30
    /** @var int maximum wait */
31
    protected $maxwait;
32
33
    /** @var int count of consecutive times the executable return error */
34
    protected $errorcount = 0;
35
36
    /** @var bool pause state of the object */
37
    protected $pause = false;
38
39
    /** @var bool flag to control main loop */
40
    protected $mainloop = true;
41
42
    /** @var PcntlSignals Native php functions (isolated for testing) */
43
    protected $pcntlsignals;
44
45
    /** @var int[] Set of signals to block and wait for */
46
    protected $signals = [SIGHUP, SIGTERM, SIGINT, SIGQUIT, SIGUSR1, SIGUSR2];
47
48
    /**
49
     * @param Executable $executable Executable object
50
     * @param Sequencer|null $sequencer Sequencer object If null then a FixedSequencer(1) will be used
51
     * @param int $maxwait Maximum seconds to wait before call again the executable object (min: 1)
52
     * @param int $minwait Minimum seconds to wait before call again the executable object (min: 0)
53
     */
54 9
    public function __construct(Executable $executable, Sequencer $sequencer = null, int $maxwait = self::DEFAULT_MAXWAIT, int $minwait = self::DEFAULT_MINWAIT)
55
    {
56 9
        $this->executable = $executable;
57 9
        if (null === $sequencer) {
58 9
            $sequencer = new FixedSequencer(1);
59
        }
60 9
        $this->sequencer = $sequencer;
61 9
        $this->setMaxWait($maxwait);
62 9
        $this->setMinWait($minwait);
63 9
        $this->pcntlsignals = new PcntlSignals($this->signals);
64 9
    }
65
66
    /**
67
     * Set the maxwait seconds, the SoftDaemon will not wait more than this quantity of seconds
68
     * Any value lower than 1 is fixed to 1, if not numeric uses default self::DEFAULT_MAXWAIT
69
     *
70
     * @param int $maxwait
71
     */
72 9
    public function setMaxWait(int $maxwait): void
73
    {
74 9
        $this->maxwait = max(1, $maxwait);
75 9
    }
76
77
    /**
78
     * Get the maxwait seconds
79
     * @return int
80
     */
81 2
    public function getMaxWait(): int
82
    {
83 2
        return $this->maxwait;
84
    }
85
86
    /**
87
     * Set the minwait seconds, the SoftDaemon will not wait less than this quantity of seconds
88
     * Any value lower than 0 is fixed to 0, if not numeric uses default 0
89
     *
90
     * @param int $minwait
91
     */
92 9
    public function setMinWait(int $minwait): void
93
    {
94 9
        $this->minwait = max(0, $minwait);
95 9
    }
96
97
    /**
98
     * Get the minwait seconds
99
     * @return int $minwait
100
     */
101 2
    public function getMinWait(): int
102
    {
103 2
        return $this->minwait;
104
    }
105
106
    /**
107
     * Reset the error counter to zero
108
     */
109 3
    public function resetErrorCounter(): void
110
    {
111 3
        $this->errorcount = 0;
112 3
    }
113
114
    /**
115
     * Will exit the main loop on the next iteration
116
     */
117 2
    public function terminate(): void
118
    {
119 2
        $this->mainloop = false;
120 2
    }
121
122
    /**
123
     * Count of consecutive times the executable return error
124
     * This value can only be set to zero using resetErrorCounter
125
     * @return int
126
     */
127 3
    public function getErrorCounter(): int
128
    {
129 3
        return $this->errorcount;
130
    }
131
132
    /**
133
     * Set the pause status, if on pause then main loop will only waiting 1 second until another signal is received
134
     * The executor is not call when the SoftDaemon is on pause
135
     * The time to wait on pause is 1 second, but this is fixed to minwait and maxwait
136
     *
137
     * @param bool $pause
138
     */
139 3
    public function setPause(bool $pause): void
140
    {
141 3
        $this->pause = $pause;
142 3
    }
143
144
    /**
145
     * Get the pause status
146
     * @return bool
147
     */
148 4
    public function getPause(): bool
149
    {
150 4
        return $this->pause;
151
    }
152
153
    /**
154
     * Fix the wait time to force minwait and maxwait
155
     *
156
     * @param int $seconds
157
     * @return int
158
     */
159 2
    protected function waitTime(int $seconds): int
160
    {
161 2
        return max($this->minwait, min($this->maxwait, $seconds));
162
    }
163
164
    /**
165
     * Run the executor expecting signals
166
     */
167 1
    public function run(): void
168
    {
169
        // reset variables
170 1
        $this->errorcount = 0;
171 1
        $this->mainloop = true;
172
        // block signals
173 1
        $this->pcntlsignals->block();
174
        // main loop
175 1
        while ($this->mainloop) {
176
            // get the time to wait based on pause or sequencer
177 1
            if ($this->getPause()) {
178 1
                $timetowait = $this->waitTime(1);
179
            } else {
180
                // get the process result
181 1
                $result = $this->executable->runOnce();
182
                // increase the error count based on result
183 1
                if ($result) {
184
                    $this->errorcount = 0;
185
                } else {
186 1
                    $this->errorcount = $this->errorcount + 1;
187
                }
188
                // calculate time to wait
189 1
                $timetowait = $this->waitTime($this->sequencer->calculate($this->errorcount));
190
            }
191
            // wait
192 1
            $signo = $this->pcntlsignals->wait($timetowait);
193 1
            if ($signo > 0) {
194 1
                $this->signalHandler($signo);
195
            }
196
        }
197
        // unblock signals
198 1
        $this->pcntlsignals->unblock();
199 1
    }
200
201
    /**
202
     * Signal processor procedure:
203
     * 1 Send the signal to executor
204
     * 2 Process the signal received
205
     *
206
     * @param int $signo
207
     */
208 3
    protected function signalHandler(int $signo): void
209
    {
210
        // send the signal handler to the executable
211 3
        $this->executable->signalHandler($signo);
212
        // process signals
213 3
        if (SIGUSR1 === $signo) { // pause
214 2
            $this->setPause(true);
215 3
        } elseif (SIGUSR2 === $signo) { // unpause
216 2
            $this->setPause(false);
217 3
        } elseif (SIGHUP === $signo) { // reset error counter
218 2
            $this->resetErrorCounter();
219 3
        } elseif (SIGTERM === $signo || SIGINT === $signo || SIGQUIT === $signo) {  // terminate
220 2
            $this->terminate();
221
        } else {
222
            // If the signal is not handled create a E_USER_WARNING
223
            // If this happends then this function is not implementing all the signals
224 1
            trigger_error(__CLASS__ . "::signalHandler($signo) do nothing", E_USER_WARNING);
225
        }
226 2
    }
227
}
228