SoftDaemon::setPause()   A
last analyzed

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