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\ExecEvent; |
||||
16 | use yii\queue\JobEvent; |
||||
17 | use yii\queue\JobInterface; |
||||
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 array of job class names that this behavior should tracks. |
||||
33 | * @since 0.3.2 |
||||
34 | */ |
||||
35 | public $only = []; |
||||
36 | /** |
||||
37 | * @var array of job class names that this behavior should not tracks. |
||||
38 | * @since 0.3.2 |
||||
39 | */ |
||||
40 | public $except = []; |
||||
41 | /** |
||||
42 | * @var array |
||||
43 | */ |
||||
44 | public $contextVars = [ |
||||
45 | '_SERVER.argv', |
||||
46 | '_SERVER.REQUEST_METHOD', |
||||
47 | '_SERVER.REQUEST_URI', |
||||
48 | '_SERVER.HTTP_REFERER', |
||||
49 | '_SERVER.HTTP_USER_AGENT', |
||||
50 | '_POST', |
||||
51 | ]; |
||||
52 | /** |
||||
53 | * @var Queue |
||||
54 | * @inheritdoc |
||||
55 | */ |
||||
56 | public $owner; |
||||
57 | /** |
||||
58 | * @var Env |
||||
59 | */ |
||||
60 | protected $env; |
||||
61 | /** |
||||
62 | * @var null|PushRecord |
||||
63 | */ |
||||
64 | protected static $startedPush; |
||||
65 | |||||
66 | /** |
||||
67 | * @param Env $env |
||||
68 | * @param array $config |
||||
69 | */ |
||||
70 | public function __construct(Env $env, $config = []) |
||||
71 | { |
||||
72 | $this->env = $env; |
||||
73 | parent::__construct($config); |
||||
74 | } |
||||
75 | |||||
76 | /** |
||||
77 | * @inheritdoc |
||||
78 | */ |
||||
79 | public function events() |
||||
80 | { |
||||
81 | return [ |
||||
82 | Queue::EVENT_AFTER_PUSH => 'afterPush', |
||||
83 | Queue::EVENT_BEFORE_EXEC => 'beforeExec', |
||||
84 | Queue::EVENT_AFTER_EXEC => 'afterExec', |
||||
85 | Queue::EVENT_AFTER_ERROR => 'afterExec', |
||||
86 | ]; |
||||
87 | } |
||||
88 | |||||
89 | /** |
||||
90 | * @param PushEvent $event |
||||
91 | */ |
||||
92 | public function afterPush(PushEvent $event) |
||||
93 | { |
||||
94 | if (!$this->isActive($event->job)) { |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
95 | return; |
||||
96 | } |
||||
97 | |||||
98 | if ($this->env->db->getTransaction()) { |
||||
99 | // create new database connection, if there is an open transaction |
||||
100 | // to ensure insert statement is not affected by a rollback |
||||
101 | $this->env->db = clone $this->env->db; |
||||
102 | } |
||||
103 | |||||
104 | $push = new PushRecord(); |
||||
105 | $push->parent_id = static::$startedPush ? static::$startedPush->id : null; |
||||
106 | $push->sender_name = $this->getSenderName($event); |
||||
107 | $push->job_uid = $event->id; |
||||
108 | $push->setJob($event->job); |
||||
109 | $push->ttr = $event->ttr; |
||||
110 | $push->delay = $event->delay; |
||||
111 | $push->trace = (new \Exception())->getTraceAsString(); |
||||
112 | $push->context = $this->getContext(); |
||||
113 | $push->pushed_at = time(); |
||||
114 | $push->save(false); |
||||
115 | } |
||||
116 | |||||
117 | /** |
||||
118 | * @param ExecEvent $event |
||||
119 | */ |
||||
120 | public function beforeExec(ExecEvent $event) |
||||
121 | { |
||||
122 | if (!$this->isActive($event->job)) { |
||||
0 ignored issues
–
show
It seems like
$event->job can also be of type null ; however, parameter $job of zhuravljov\yii\queue\mon...\JobMonitor::isActive() does only seem to accept yii\queue\JobInterface , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
123 | return; |
||||
124 | } |
||||
125 | static::$startedPush = $push = $this->getPushRecord($event); |
||||
126 | if (!$push) { |
||||
0 ignored issues
–
show
|
|||||
127 | return; |
||||
128 | } |
||||
129 | if ($push->isStopped()) { |
||||
130 | // Rejects job execution in case is stopped |
||||
131 | $event->handled = true; |
||||
132 | return; |
||||
133 | } |
||||
134 | $this->env->db->transaction(function () use ($event, $push) { |
||||
135 | $worker = $this->getWorkerRecord($event); |
||||
0 ignored issues
–
show
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
136 | |||||
137 | $exec = new ExecRecord(); |
||||
138 | $exec->push_id = $push->id; |
||||
139 | if ($worker) { |
||||
0 ignored issues
–
show
|
|||||
140 | $exec->worker_id = $worker->id; |
||||
141 | } |
||||
142 | $exec->attempt = $event->attempt; |
||||
143 | $exec->started_at = time(); |
||||
144 | $exec->save(false); |
||||
145 | |||||
146 | $push->first_exec_id = $push->first_exec_id ?: $exec->id; |
||||
147 | $push->last_exec_id = $exec->id; |
||||
148 | $push->save(false); |
||||
149 | |||||
150 | if ($worker) { |
||||
0 ignored issues
–
show
|
|||||
151 | $worker->last_exec_id = $exec->id; |
||||
152 | $worker->save(false); |
||||
153 | } |
||||
154 | }); |
||||
155 | } |
||||
156 | |||||
157 | /** |
||||
158 | * @param ExecEvent $event |
||||
159 | */ |
||||
160 | public function afterExec(ExecEvent $event) |
||||
161 | { |
||||
162 | if (!$this->isActive($event->job)) { |
||||
0 ignored issues
–
show
It seems like
$event->job can also be of type null ; however, parameter $job of zhuravljov\yii\queue\mon...\JobMonitor::isActive() does only seem to accept yii\queue\JobInterface , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
163 | return; |
||||
164 | } |
||||
165 | $push = static::$startedPush ?: $this->getPushRecord($event); |
||||
166 | if (!$push) { |
||||
0 ignored issues
–
show
|
|||||
167 | return; |
||||
168 | } |
||||
169 | if ($push->isStopped()) { |
||||
170 | // Breaks retry in case is stopped |
||||
171 | $event->retry = false; |
||||
172 | } |
||||
173 | if ($push->last_exec_id) { |
||||
0 ignored issues
–
show
The expression
$push->last_exec_id of type integer|null 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 For 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
![]() |
|||||
174 | ExecRecord::updateAll([ |
||||
175 | 'finished_at' => time(), |
||||
176 | 'memory_usage' => static::$startedPush ? memory_get_peak_usage() : null, |
||||
177 | 'error' => $event->error, |
||||
178 | 'result_data' => $event->result !== null ? serialize($event->result) : null, |
||||
179 | 'retry' => (bool) $event->retry, |
||||
180 | ], [ |
||||
181 | 'id' => $push->last_exec_id |
||||
182 | ]); |
||||
183 | } |
||||
184 | } |
||||
185 | |||||
186 | /** |
||||
187 | * @param JobInterface $job |
||||
188 | * @return bool |
||||
189 | * @since 0.3.2 |
||||
190 | */ |
||||
191 | protected function isActive(JobInterface $job) |
||||
192 | { |
||||
193 | $onlyMatch = true; |
||||
194 | if ($this->only) { |
||||
0 ignored issues
–
show
The expression
$this->only of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
195 | $onlyMatch = false; |
||||
196 | foreach ($this->only as $className) { |
||||
197 | if (is_a($job, $className)) { |
||||
198 | $onlyMatch = true; |
||||
199 | break; |
||||
200 | } |
||||
201 | } |
||||
202 | } |
||||
203 | |||||
204 | $exceptMatch = false; |
||||
205 | foreach ($this->except as $className) { |
||||
206 | if (is_a($job, $className)) { |
||||
207 | $exceptMatch = true; |
||||
208 | break; |
||||
209 | } |
||||
210 | } |
||||
211 | |||||
212 | return !$exceptMatch && $onlyMatch; |
||||
213 | } |
||||
214 | |||||
215 | /** |
||||
216 | * @param JobEvent $event |
||||
217 | * @throws |
||||
218 | * @return string |
||||
219 | */ |
||||
220 | protected function getSenderName($event) |
||||
221 | { |
||||
222 | foreach (Yii::$app->getComponents(false) as $id => $component) { |
||||
223 | if ($component === $event->sender) { |
||||
224 | return $id; |
||||
225 | } |
||||
226 | } |
||||
227 | throw new InvalidConfigException('Queue must be an application component.'); |
||||
228 | } |
||||
229 | |||||
230 | /** |
||||
231 | * @return string |
||||
232 | */ |
||||
233 | protected function getContext() |
||||
234 | { |
||||
235 | $context = ArrayHelper::filter($GLOBALS, $this->contextVars); |
||||
236 | $result = []; |
||||
237 | foreach ($context as $key => $value) { |
||||
238 | $result[] = "\${$key} = " . VarDumper::dumpAsString($value); |
||||
239 | } |
||||
240 | |||||
241 | return implode("\n\n", $result); |
||||
242 | } |
||||
243 | |||||
244 | /** |
||||
245 | * @param JobEvent $event |
||||
246 | * @return PushRecord |
||||
247 | */ |
||||
248 | protected function getPushRecord(JobEvent $event) |
||||
249 | { |
||||
250 | if ($event->id !== null) { |
||||
251 | return $this->env->db->useMaster(function () use ($event) { |
||||
252 | return PushRecord::find() |
||||
253 | ->byJob($this->getSenderName($event), $event->id) |
||||
254 | ->one(); |
||||
255 | }); |
||||
256 | } else { |
||||
257 | return null; |
||||
258 | } |
||||
259 | } |
||||
260 | |||||
261 | /** |
||||
262 | * @param ExecEvent $event |
||||
263 | * @return WorkerRecord|null |
||||
264 | */ |
||||
265 | protected function getWorkerRecord(ExecEvent $event) |
||||
266 | { |
||||
267 | if ($event->sender->getWorkerPid() === null) { |
||||
0 ignored issues
–
show
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
268 | return null; |
||||
269 | } |
||||
270 | if (!$this->isWorkerMonitored()) { |
||||
271 | return null; |
||||
272 | } |
||||
273 | |||||
274 | return $this->env->db->useMaster(function () use ($event) { |
||||
275 | return WorkerRecord::find() |
||||
276 | ->byEvent($this->env->getHost(), $event->sender->getWorkerPid()) |
||||
277 | ->active() |
||||
278 | ->one(); |
||||
279 | }); |
||||
280 | } |
||||
281 | |||||
282 | /** |
||||
283 | * @return bool whether workers are monitored. |
||||
284 | */ |
||||
285 | private function isWorkerMonitored() |
||||
286 | { |
||||
287 | foreach ($this->owner->getBehaviors() as $behavior) { |
||||
288 | if ($behavior instanceof WorkerMonitor) { |
||||
289 | return true; |
||||
290 | } |
||||
291 | } |
||||
292 | return false; |
||||
293 | } |
||||
294 | } |
||||
295 |