Completed
Push — master ( aaeb57...138031 )
by Marco
11:42
created

Manager::install()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 0
cts 18
cp 0
rs 8.9713
c 0
b 0
f 0
cc 2
eloc 15
nc 2
nop 3
crap 6
1
<?php namespace Comodojo\Daemon\Worker;
2
3
use \Comodojo\Daemon\Daemon;
4
use \Comodojo\Daemon\Utils\ProcessTools;
5
use \Comodojo\Daemon\Utils\PosixSignals;
6
use \Comodojo\Foundation\Events\EventsTrait;
7
use \Comodojo\Foundation\Logging\LoggerTrait;
8
use \Comodojo\Foundation\Events\Manager as EventsManager;
9
use \Comodojo\Foundation\DataAccess\IteratorTrait;
10
use \Comodojo\Foundation\DataAccess\CountableTrait;
11
use \Psr\Log\LoggerInterface;
12
use \Iterator;
13
use \Countable;
14
use \Exception;
15
16
/**
17
 * @package     Comodojo Daemon
18
 * @author      Marco Giovinazzi <[email protected]>
19
 * @license     MIT
20
 *
21
 * LICENSE:
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
 * THE SOFTWARE.
30
 */
31
32
 class Manager implements Iterator, Countable {
33
34
    use IteratorTrait;
35
    use CountableTrait;
36
    use EventsTrait;
37
    use LoggerTrait;
38
39
    /**
40
     * The stack :)
41
     *
42
     * @var array
43
     */
44
    private $data = [];
45
46
    /**
47
     * Pointer to parent daemon
48
     *
49
     * @var Daemon
50
     */
51
    private $daemon;
52
53
    /**
54
     * Manager constructor
55
     *
56
     * @param LoggerInterface $logger;
0 ignored issues
show
Documentation introduced by
There is no parameter named $logger;. Did you maybe mean $logger?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
57
     * @param EventsManager $events;
0 ignored issues
show
Documentation introduced by
There is no parameter named $events;. Did you maybe mean $events?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
58
     * @param Daemon $daemon;
0 ignored issues
show
Documentation introduced by
There is no parameter named $daemon;. Did you maybe mean $daemon?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
59
     */
60
    public function __construct(
61
        LoggerInterface $logger,
62
        EventsManager $events,
63
        Daemon $daemon
64
    ) {
65
66
        $this->logger = $logger;
67
        $this->events = $events;
68
        $this->daemon = $daemon;
69
70
    }
71
72
    /**
73
     * Install a worker into the stack
74
     *
75
     * @param WorkerInterface $worker
76
     * @param int $looptime
77
     * @param bool $forever
78
     * @return Manager
79
     */
80
    public function install(
81
        WorkerInterface $worker,
82
        $looptime = 1,
83
        $forever = false
84
    ) {
85
86
        $name = $worker->getName();
87
88
        if ( $this->isInstalled($name) ) {
89
            throw new Exception("Worker already installed");
90
        }
91
92
        $w = Worker::create()
93
            ->setInstance($worker)
94
            ->setLooptime($looptime)
95
            ->setForever($forever)
96
            ->setInputChannel(new SharedMemory((int)'1'.hexdec($worker->getId())))
97
            ->setOutputChannel(new SharedMemory((int)'2'.hexdec($worker->getId())));
98
99
        $this->data[$name] = $w;
100
101
        return $this;
102
103
    }
104
105
    public function setPid($name, $pid) {
106
107
        if ( !$this->isInstalled($name) ) {
108
            throw new Exception("Worker not installed");
109
        }
110
111
        $this->data[$name]->setPid($pid);
112
113
        return $this;
114
115
    }
116
117
    public function get($name = null) {
118
119
        if ( is_null($name) ) return $this->data;
120
121
        if ( !$this->isInstalled($name) ) {
122
            throw new Exception("Worker not installed");
123
        }
124
125
        return $this->data[$name];
126
127
    }
128
129
    public function isInstalled($name) {
130
131
        return array_key_exists($name, $this->data);
132
133
    }
134
135
    public function start($name, $unmask = false) {
136
137
        // fork worker
138
        $pid = pcntl_fork();
139
140
        if ( $pid == -1 ) {
141
            $this->logger->error("Could not create worker $name (fork error)");
142
            $daemon->end(1);
0 ignored issues
show
Bug introduced by
The variable $daemon seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
143
        }
144
145
        if ( $pid ) {
146
            $this->logger->notice("Worker $name created with pid $pid");
147
            $this->setPid($name, $pid);
148
            return;
149
        }
150
151
        // get daemon and worker
152
        $worker = $this->get($name);
153
        $daemon = $this->daemon;
154
155
        // update pid reference
156
        $daemon->setPid(ProcessTools::getPid());
157
158
        // cleanup events
159
        $daemon->getSignals()->any()->setDefault();
160
161
        // unmask signals (if restart)
162
        if ( $unmask === true ) {
163
            $daemon->getSignals()->any()->unmask();
164
        }
165
166
        // inject events, logger and signals
167
        $logger = $daemon->getLogger()->withName($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Log\LoggerInterface as the method withName() does only exist in the following implementations of said interface: Monolog\Logger.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
168
        // $events = new EventsManager($logger);
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
169
        $events = clone $this->getEvents();
170
        $signals = new PosixSignals;
171
        $worker->getInstance()->setLogger($logger);
172
        $worker->getInstance()->setEvents($events);
173
        $worker->getInstance()->setSignals($signals);
174
175
        $loop = new Loop($worker);
176
177
        $daemon->declass();
178
179
        $loop->start();
180
181
        $daemon->end(0);
182
183
    }
184
185
    public function stop($name = null) {
186
187
        foreach ($this->data as $wname => $worker) {
188
189
            $wpid = $worker->getPid();
190
191
            if ( is_null($name) || $name == $wname ) {
192
193
                // fix the wait time;
194
                $time = time() + 5;
195
196
                // try to gently ask the worker to close
197
                $worker->getOutputChannel()->send('stop');
198
199
                while (time() < $time) {
200
201
                    if ( !$this->running($wpid) ) break;
202
                    usleep(20000);
203
204
                }
205
206
                // close the shared memory block
207
                $worker->getInputChannel()->close();
208
                $worker->getOutputChannel()->close();
209
210
                // terminate the worker if still alive
211
                if ($this->running($wpid)) ProcessTools::term($wpid, 5, SIGTERM);
212
213
            }
214
215
        }
216
217
    }
218
219 View Code Duplication
    public function pause($name = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220
221
        if ( empty($name) ) {
222
            $result = [];
223
            foreach ($this->data as $name => $worker) {
224
                $result[$name] = $worker->getOutputChannel()->send('pause') > 0;
225
            }
226
            return $result;
227
        }
228
229
        return $this->get($name)->getOutputChannel()->send('pause') > 0;
230
231
    }
232
233 View Code Duplication
    public function resume($name = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
234
235
        if ( empty($name) ) {
236
            $result = [];
237
            foreach ($this->data as $name => $worker) {
238
                $result[$name] = $worker->getOutputChannel()->send('resume') > 0;
239
            }
240
            return $result;
241
        }
242
243
        return $this->get($name)->getOutputChannel()->send('resume') > 0;
244
245
    }
246
247
    public function running($pid) {
248
249
        return ProcessTools::isRunning($pid);
250
251
    }
252
253
    public function status($name = null) {
254
255
        if ( $name === null ) {
256
257
            $result = [];
258
            foreach ($this->data as $name => $worker) {
259
                $result[$name] = $this->getStatus($worker);
260
            }
261
            return $result;
262
263
        }
264
265
        $worker = $this->get($name);
266
267
        return $this->getStatus($worker);
268
269
    }
270
271
    private function getStatus(Worker $worker) {
272
273
        return $worker->getInputChannel()->read();
274
275
    }
276
277
 }
278