Passed
Push — master ( b313ea...47c9db )
by Roman
03:00
created

JobMonitor::isWorkerMonitored()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link https://github.com/zhuravljov/yii2-queue-monitor
4
 * @copyright Copyright (c) 2017 Roman Zhuravlev
5
 * @license http://opensource.org/licenses/BSD-3-Clause
6
 */
7
8
namespace zhuravljov\yii\queue\monitor;
9
10
use Yii;
11
use yii\base\Behavior;
12
use yii\base\InvalidConfigException;
13
use yii\helpers\ArrayHelper;
14
use yii\helpers\VarDumper;
15
use yii\queue\ErrorEvent;
16
use yii\queue\ExecEvent;
17
use yii\queue\JobEvent;
18
use yii\queue\PushEvent;
19
use yii\queue\Queue;
20
use zhuravljov\yii\queue\monitor\records\ExecRecord;
21
use zhuravljov\yii\queue\monitor\records\PushRecord;
22
use zhuravljov\yii\queue\monitor\records\WorkerRecord;
23
24
/**
25
 * Queue Job Monitor
26
 *
27
 * @author Roman Zhuravlev <[email protected]>
28
 */
29
class JobMonitor extends Behavior
30
{
31
    /**
32
     * @var Queue
33
     * @inheritdoc
34
     */
35
    public $owner;
36
    /**
37
     * @var array
38
     */
39
    public $contextVars = ['_SERVER'];
40
    /**
41
     * @var Env
42
     */
43
    protected $env;
44
    /**
45
     * @var null|PushRecord
46
     */
47
    protected static $startedPush;
48
49
    /**
50
     * @param Env $env
51
     * @param array $config
52
     */
53
    public function __construct(Env $env, $config = [])
54
    {
55
        $this->env = $env;
56
        parent::__construct($config);
57
    }
58
59
    /**
60
     * @inheritdoc
61
     */
62
    public function events()
63
    {
64
        return [
65
            Queue::EVENT_AFTER_PUSH => 'afterPush',
66
            Queue::EVENT_BEFORE_EXEC => 'beforeExec',
67
            Queue::EVENT_AFTER_EXEC => 'afterExec',
68
            Queue::EVENT_AFTER_ERROR => 'afterError',
69
        ];
70
    }
71
72
    /**
73
     * @param PushEvent $event
74
     */
75
    public function afterPush(PushEvent $event)
76
    {
77
        $push = new PushRecord();
78
        $push->parent_id = static::$startedPush ? static::$startedPush->id : null;
79
        $push->sender_name = $this->getSenderName($event);
80
        $push->job_uid = $event->id;
81
        $push->setJob($event->job);
82
        $push->ttr = $event->ttr;
83
        $push->delay = $event->delay;
84
        $push->trace = (new \Exception())->getTraceAsString();
85
        $push->context = $this->getContext();
86
        $push->pushed_at = time();
87
        $push->save(false);
88
    }
89
90
    /**
91
     * @param ExecEvent $event
92
     */
93
    public function beforeExec(ExecEvent $event)
94
    {
95
        static::$startedPush = $push = $this->getPushRecord($event);
96
        if (!$push) {
0 ignored issues
show
introduced by
$push is of type zhuravljov\yii\queue\monitor\records\PushRecord, thus it always evaluated to true.
Loading history...
97
            return;
98
        }
99
        if ($push->isStopped()) {
100
            // Rejects job execution in case is stopped
101
            $event->handled = true;
102
            return;
103
        }
104
        $this->env->db->transaction(function () use ($event, $push) {
105
            $worker = $this->getWorkerRecord($event);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $worker is correct as $this->getWorkerRecord($event) targeting zhuravljov\yii\queue\mon...itor::getWorkerRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
106
107
            $exec = new ExecRecord();
108
            $exec->push_id = $push->id;
109
            if ($worker) {
0 ignored issues
show
introduced by
$worker is of type null, thus it always evaluated to false.
Loading history...
110
                $exec->worker_id = $worker->id;
111
            }
112
            $exec->attempt = $event->attempt;
113
            $exec->started_at = time();
114
            $exec->save(false);
115
116
            $push->first_exec_id = $push->first_exec_id ?: $exec->id;
117
            $push->last_exec_id = $exec->id;
118
            $push->save(false);
119
120
            if ($worker) {
0 ignored issues
show
introduced by
$worker is of type null, thus it always evaluated to false.
Loading history...
121
                $worker->last_exec_id = $exec->id;
122
                $worker->save(false);
123
            }
124
        });
125
    }
126
127
    /**
128
     * @param ExecEvent $event
129
     */
130
    public function afterExec(ExecEvent $event)
131
    {
132
        $push = static::$startedPush ?: $this->getPushRecord($event);
133
        if (!$push) {
0 ignored issues
show
introduced by
$push is of type zhuravljov\yii\queue\monitor\records\PushRecord, thus it always evaluated to true.
Loading history...
134
            return;
135
        }
136
        if ($push->last_exec_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $push->last_exec_id of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
137
            ExecRecord::updateAll([
138
                'finished_at' => time(),
139
                'memory_usage' => memory_get_peak_usage(),
140
                'error' => null,
141
                'retry' => false,
142
            ], [
143
                'id' => $push->last_exec_id
144
            ]);
145
        }
146
    }
147
148
    /**
149
     * @param ErrorEvent $event
150
     */
151
    public function afterError(ErrorEvent $event)
152
    {
153
        $push = static::$startedPush ?: $this->getPushRecord($event);
154
        if (!$push) {
0 ignored issues
show
introduced by
$push is of type zhuravljov\yii\queue\monitor\records\PushRecord, thus it always evaluated to true.
Loading history...
155
            return;
156
        }
157
        if ($push->isStopped()) {
158
            // Breaks retry in case is stopped
159
            $event->retry = false;
160
        }
161
        if ($push->last_exec_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $push->last_exec_id of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
162
            ExecRecord::updateAll([
163
                'finished_at' => time(),
164
                'memory_usage' => static::$startedPush ? memory_get_peak_usage() : null,
165
                'error' => $event->error,
166
                'retry' => $event->retry,
167
            ], [
168
                'id' => $push->last_exec_id
169
            ]);
170
        }
171
    }
172
173
    /**
174
     * @param JobEvent $event
175
     * @throws
176
     * @return string
177
     */
178
    protected function getSenderName($event)
179
    {
180
        foreach (Yii::$app->getComponents(false) as $id => $component) {
181
            if ($component === $event->sender) {
182
                return $id;
183
            }
184
        }
185
        throw new InvalidConfigException('Queue must be an application component.');
186
    }
187
188
    /**
189
     * @return string
190
     */
191
    protected function getContext()
192
    {
193
        $context = ArrayHelper::filter($GLOBALS, $this->contextVars);
194
        $result = [];
195
        foreach ($context as $key => $value) {
196
            $result[] = "\${$key} = " . VarDumper::dumpAsString($value);
197
        }
198
199
        return implode("\n\n", $result);
200
    }
201
202
    /**
203
     * @param JobEvent $event
204
     * @return PushRecord
205
     */
206
    protected function getPushRecord(JobEvent $event)
207
    {
208
        if ($event->id !== null) {
209
            return $this->env->db->useMaster(function () use ($event) {
210
                return PushRecord::find()
211
                    ->byJob($this->getSenderName($event), $event->id)
212
                    ->one();
213
            });
214
        } else {
215
            return null;
216
        }
217
    }
218
219
    /**
220
     * @param ExecEvent $event
221
     * @return WorkerRecord|null
222
     */
223
    protected function getWorkerRecord(ExecEvent $event)
224
    {
225
        if ($event->sender->getWorkerPid() === null) {
0 ignored issues
show
introduced by
The condition $event->sender->getWorkerPid() === null is always true.
Loading history...
Bug introduced by
Are you sure the usage of $event->sender->getWorkerPid() targeting yii\queue\Queue::getWorkerPid() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
226
            return null;
227
        }
228
        if (!$this->isWorkerMonitored()) {
229
            return null;
230
        }
231
232
        return $this->env->db->useMaster(function () use ($event) {
233
            return WorkerRecord::find()
234
                ->byEvent($this->env->getHost(), $event->sender->getWorkerPid())
235
                ->active()
236
                ->one();
237
        });
238
    }
239
240
    /**
241
     * @return bool whether workers are monitored.
242
     */
243
    private function isWorkerMonitored()
244
    {
245
        foreach ($this->owner->getBehaviors() as $behavior) {
246
            if ($behavior instanceof WorkerMonitor) {
247
                return true;
248
            }
249
        }
250
        return false;
251
    }
252
}
253