worker   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 404
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 153
c 3
b 0
f 0
dl 0
loc 404
rs 3.12
wmc 66

13 Methods

Rating   Name   Duplication   Size   Complexity  
A fork_one_worker() 0 43 4
A stop() 0 8 2
B set_process_user() 0 13 7
C get_error_type() 0 36 16
A stop_all() 0 19 3
B check_errors() 0 16 8
B monitor_workers() 0 69 10
A run() 0 24 4
A uninstall_signal() 0 8 1
A set_process_title() 0 13 5
A signal_handler() 0 15 4
A __construct() 0 6 1
A install_signal() 0 10 1

How to fix   Complexity   

Complex Class

Complex classes like worker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use worker, and based on these observations, apply Extract Interface, too.

1
<?php
2
// +----------------------------------------------------------------------
3
// | PHPSpider [ A PHP Framework For Crawler ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: Seatle Yang <[email protected]>
10
// +----------------------------------------------------------------------
11
12
//----------------------------------
13
// Worker多进程操作类
14
//----------------------------------
15
16
class worker
17
{
18
    // worker进程数
19
    public $count = 0;
20
    // worker id,worker进程从1开始,0被master进程所使用
21
    public $worker_id = 0;
22
    // worker 进程ID
23
    public $worker_pid = 0;
24
    // 进程用户
25
    public $user = '';
26
    // 进程名
27
    public $title = '';
28
    // 每个进程是否只运行一次
29
    public $run_once = true;
30
    // 是否输出日志
31
    public $log_show = false;
32
    // master进程启动回调
33
    public $on_start = false;
34
    // master进程停止回调
35
    public $on_stop = false;
36
    // worker进程启动回调
37
    public $on_worker_start = false;
38
    // worker进程停止回调
39
    public $on_worker_stop = false;
40
    // master进程ID
41
    protected static $_master_pid = 0;
42
    // worker进程ID
43
    protected static $_worker_pids = array();
44
    // master、worker进程启动时间
45
    public $time_start = 0;
46
    // master、worker进程运行状态 [starting|running|shutdown|reload]
47
    protected static $_status = "starting";
48
49
50
    public function __construct()
51
    {
52
        self::$_master_pid = posix_getpid();
53
        // 产生时钟云,添加后父进程才可以收到信号
54
        declare(ticks = 1);
55
        $this->install_signal();
0 ignored issues
show
Bug introduced by
The method install_signal() does not exist on Worker. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

55
        $this->/** @scrutinizer ignore-call */ 
56
               install_signal();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
56
    }
57
58
    /**
59
     * 安装信号处理函数
60
     * @return void
61
     */
62
    protected function install_signal()
63
    {
64
        // stop
65
        pcntl_signal(SIGINT,  array($this, 'signal_handler'), false);
66
        // reload
67
        pcntl_signal(SIGUSR1, array($this, 'signal_handler'), false);
68
        // status
69
        pcntl_signal(SIGUSR2, array($this, 'signal_handler'), false);
70
        // ignore
71
        pcntl_signal(SIGPIPE, SIG_IGN, false);
72
        // install signal handler for dead kids
73
        // pcntl_signal(SIGCHLD, array($this, 'signal_handler'));
74
    }
75
76
    /**
77
     * 卸载信号处理函数
78
     * @return void
79
     */
80
    protected function uninstall_signal()
81
    {
82
        // uninstall stop signal handler
83
        pcntl_signal(SIGINT,  SIG_IGN, false);
84
        // uninstall reload signal handler
85
        pcntl_signal(SIGUSR1, SIG_IGN, false);
86
        // uninstall  status signal handler
87
        pcntl_signal(SIGUSR2, SIG_IGN, false);
88
    }
89
90
    /**
91
     * 信号处理函数,会被其他类调用到,所以要设置为public
92
     * @param int $signal
93
     */
94
    public function signal_handler($signal) {
95
        switch ($signal) {
96
            // stop 2
97
            case SIGINT:
98
                // master进程和worker进程都会调用
99
                $this->stop_all();
0 ignored issues
show
Bug introduced by
The method stop_all() does not exist on Worker. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

99
                $this->/** @scrutinizer ignore-call */ 
100
                       stop_all();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
100
                break;
101
            // reload 30
102
            case SIGUSR1:
103
                echo "reload\n";
104
                break;
105
            // show status 31
106
            case SIGUSR2:   
107
                echo "status\n";
108
                break;
109
        }
110
    }
111
112
    /**
113
     * 运行worker实例
114
     */
115
    public function run()
116
    {
117
        $this->time_start = microtime(true); 
118
        $this->worker_id = 0;
0 ignored issues
show
Bug introduced by
The property worker_id does not exist on Worker. Did you mean worker?
Loading history...
119
        $this->worker_pid = posix_getpid();
0 ignored issues
show
Bug introduced by
The property worker_pid does not exist on Worker. Did you mean worker?
Loading history...
120
        $this->set_process_title($this->title);
0 ignored issues
show
Bug introduced by
The method set_process_title() does not exist on Worker. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

120
        $this->/** @scrutinizer ignore-call */ 
121
               set_process_title($this->title);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug Best Practice introduced by
The property title does not exist on Worker. Did you maybe forget to declare it?
Loading history...
121
122
        // 这里赋值,worker进程也会克隆到
123
        if ($this->log_show) 
0 ignored issues
show
Bug Best Practice introduced by
The property log_show does not exist on Worker. Did you maybe forget to declare it?
Loading history...
124
        {
125
            log::$log_show = true;
126
        }
127
128
        if ($this->on_start) 
0 ignored issues
show
Bug Best Practice introduced by
The property on_start does not exist on Worker. Did you maybe forget to declare it?
Loading history...
129
        {
130
            call_user_func($this->on_start, $this);
131
        }
132
133
        // worker进程从1开始,0被master进程所使用
134
        for ($i = 1; $i <= $this->count; $i++) 
0 ignored issues
show
Bug Best Practice introduced by
The property count does not exist on Worker. Did you maybe forget to declare it?
Loading history...
135
        {
136
            $this->fork_one_worker($i);
0 ignored issues
show
Bug introduced by
The method fork_one_worker() does not exist on Worker. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

136
            $this->/** @scrutinizer ignore-call */ 
137
                   fork_one_worker($i);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
137
        }
138
        $this->monitor_workers();
0 ignored issues
show
Bug introduced by
The method monitor_workers() does not exist on Worker. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

138
        $this->/** @scrutinizer ignore-call */ 
139
               monitor_workers();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
139
    }
140
141
    /**
142
     * 创建一个子进程
143
     * @param Worker $worker
144
     * @throws Exception
145
     */
146
    public function fork_one_worker($worker_id)
147
    {
148
        //$sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
149
        $pid = pcntl_fork();
150
151
        // 主进程记录子进程pid
152
        if($pid > 0)
153
        {
154
            self::$_worker_pids[$worker_id] = $pid;
0 ignored issues
show
Bug introduced by
The property _worker_pids does not exist on Worker. Did you mean worker?
Loading history...
155
        }
156
        // 子进程运行
157
        elseif(0 === $pid)
158
        {
159
            $this->time_start = microtime(true);
160
            $this->worker_id = $worker_id;
0 ignored issues
show
Bug introduced by
The property worker_id does not exist on Worker. Did you mean worker?
Loading history...
161
            $this->worker_pid = posix_getpid();
0 ignored issues
show
Bug introduced by
The property worker_pid does not exist on Worker. Did you mean worker?
Loading history...
162
            $this->set_process_title($this->title);
0 ignored issues
show
Bug Best Practice introduced by
The property title does not exist on Worker. Did you maybe forget to declare it?
Loading history...
163
            $this->set_process_user($this->user);
0 ignored issues
show
Bug introduced by
The method set_process_user() does not exist on Worker. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

163
            $this->/** @scrutinizer ignore-call */ 
164
                   set_process_user($this->user);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug Best Practice introduced by
The property user does not exist on Worker. Did you maybe forget to declare it?
Loading history...
164
            // 清空master进程克隆过来的worker进程ID
165
            self::$_worker_pids = array();
166
            //$this->uninstall_signal();
167
168
            // 设置worker进程的运行状态为运行中
169
            self::$_status = "running";
170
171
            // 注册进程退出回调,用来检查是否有错误(子进程里面注册)
172
            register_shutdown_function(array($this, 'check_errors'));
173
174
            // 如果设置了worker进程启动回调函数
175
            if ($this->on_worker_start) 
0 ignored issues
show
Bug introduced by
The property on_worker_start does not exist on Worker. Did you mean worker?
Loading history...
176
            {
177
                call_user_func($this->on_worker_start, $this);
178
            }
179
180
            // 停止当前worker实例
181
            $this->stop();
0 ignored issues
show
Bug introduced by
The method stop() does not exist on Worker. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

181
            $this->/** @scrutinizer ignore-call */ 
182
                   stop();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
182
            // 这里用0表示正常退出
183
            exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
184
        }
185
        else
186
        {
187
            log::add("fork one worker fail", "Error");
188
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
189
        }
190
    }
191
192
    /**
193
     * 尝试设置运行当前进程的用户
194
     *
195
     * @param $user_name
196
     */
197
    protected static function set_process_user($user_name)
198
    {
199
        // 用户名为空 或者 当前用户不是root用户
200
        if(empty($user_name) || posix_getuid() !== 0)
201
        {
202
            return;
203
        }
204
        $user_info = posix_getpwnam($user_name);
205
        if($user_info['uid'] != posix_getuid() || $user_info['gid'] != posix_getgid())
206
        {
207
            if(!posix_setgid($user_info['gid']) || !posix_setuid($user_info['uid']))
208
            {
209
                log::add('Can not run woker as '.$user_name." , You shuld be root", "Error");
210
            }
211
        }
212
    }
213
214
    /**
215
     * 设置当前进程的名称,在ps aux命令中有用
216
     * 注意 需要php>=5.5或者安装了protitle扩展
217
     * @param string $title
218
     * @return void
219
     */
220
    protected function set_process_title($title)
221
    {
222
        if (!empty($title)) 
223
        {
224
            // 需要扩展
225
            if(extension_loaded('proctitle') && function_exists('setproctitle'))
226
            {
227
                @setproctitle($title);
228
            }
229
            // >=php 5.5
230
            elseif (function_exists('cli_set_process_title'))
231
            {
232
                cli_set_process_title($title);
233
            }
234
        }
235
    }
236
237
    /**
238
     * 监控所有子进程的退出事件及退出码
239
     * @return void
240
     */
241
    public function monitor_workers()
242
    {
243
        // 设置master进程的运行状态为运行中
244
        self::$_status = "running";
245
        while(1)
246
        {
247
            // pcntl_signal_dispatch 子进程无法接受到信号
248
            // 如果有信号到来,尝试触发信号处理函数
249
            //pcntl_signal_dispatch();
250
            // 挂起进程,直到有子进程退出或者被信号打断
251
            $status = 0;
252
            $pid = pcntl_wait($status, WUNTRACED);
253
            // 如果有信号到来,尝试触发信号处理函数
254
            //pcntl_signal_dispatch();
255
256
            // 子进程退出信号
257
            if($pid > 0)
258
            {
259
                //echo "worker[".$pid."] stop\n";
260
                //$this->stop();
261
262
                // 如果不是正常退出,是被kill等杀掉的
263
                if($status !== 0)
264
                {
265
                    log::add("worker {$pid} exit with status $status", "Warning");
266
                }
267
268
                // key 和 value 互换
269
                $worker_pids = array_flip(self::$_worker_pids);
0 ignored issues
show
Bug introduced by
The property _worker_pids does not exist on Worker. Did you mean worker?
Loading history...
270
                // 通过 pid 得到 worker_id
271
                $worker_id = $worker_pids[$pid];
272
                // 这里不unset掉,是为了进程重启
273
                self::$_worker_pids[$worker_id] = 0;
274
                //unset(self::$_worker_pids[$pid]);
275
276
                // 再生成一个worker
277
                if (!$this->run_once) 
0 ignored issues
show
Bug Best Practice introduced by
The property run_once does not exist on Worker. Did you maybe forget to declare it?
Loading history...
278
                {
279
                    $this->fork_one_worker($worker_id);
280
                }
281
282
                // 如果所有子进程都退出了,触发主进程退出函数
283
                $all_worker_stop = true;
284
                foreach (self::$_worker_pids as $_worker_pid) 
285
                {
286
                    // 只要有一个worker进程还存在进程ID,就不算退出
287
                    if ($_worker_pid != 0) 
288
                    {
289
                        $all_worker_stop = false;
290
                    }
291
                }
292
                if ($all_worker_stop) 
293
                {
294
                    if ($this->on_stop) 
0 ignored issues
show
Bug Best Practice introduced by
The property on_stop does not exist on Worker. Did you maybe forget to declare it?
Loading history...
295
                    {
296
                        call_user_func($this->on_stop, $this);
297
                    }
298
                    exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
299
                }
300
            }
301
            // 其他信号
302
            else 
303
            {
304
                // worker进程接受到master进行信号退出的,会到这里来
305
                if ($this->on_stop) 
306
                {
307
                    call_user_func($this->on_stop, $this);
308
                }
309
                exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
310
            }
311
        }
312
    }
313
314
    /**
315
     * 执行关闭流程(所有进程)
316
     * 事件触发,非正常程序执行完毕
317
     * @return void
318
     */
319
    public function stop_all()
320
    {
321
        // 设置master、worker进程的运行状态为关闭状态
322
        self::$_status = "shutdown";
323
        // master进程
324
        if(self::$_master_pid === posix_getpid())
0 ignored issues
show
Bug Best Practice introduced by
The property _master_pid does not exist on Worker. Did you maybe forget to declare it?
Loading history...
325
        {
326
            // 循环给worker进程发送关闭信号
327
            foreach (self::$_worker_pids as $worker_pid) 
0 ignored issues
show
Bug introduced by
The property _worker_pids does not exist on Worker. Did you mean worker?
Loading history...
328
            {
329
                posix_kill($worker_pid, SIGINT);
330
            }
331
        }
332
        // worker进程
333
        else 
334
        {
335
            // 接收到master进程发送的关闭信号之后退出,这里应该考虑业务的完整性,不能强行exit
336
            $this->stop();
337
            exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
338
        }
339
    }
340
341
    /**
342
     * 停止当前worker实例
343
     * 正常运行结束和接受信号退出,都会调用这个方法
344
     * @return void
345
     */
346
    public function stop()
347
    {
348
        if ($this->on_worker_stop) 
0 ignored issues
show
Bug introduced by
The property on_worker_stop does not exist on Worker. Did you mean worker?
Loading history...
349
        {
350
            call_user_func($this->on_worker_stop, $this);
351
        }
352
        // 设置worker进程的运行状态为关闭
353
        self::$_status = "shutdown";
354
    }
355
356
    /**
357
     * 检查错误,PHP exit之前会执行
358
     * @return void
359
     */
360
    public function check_errors()
361
    {
362
        // 如果当前worker进程不是正常退出
363
        if(self::$_status != "shutdown")
0 ignored issues
show
Bug Best Practice introduced by
The property _status does not exist on Worker. Did you maybe forget to declare it?
Loading history...
364
        {
365
            $error_msg = "WORKER EXIT UNEXPECTED ";
366
            $errors = error_get_last();
367
            if($errors && ($errors['type'] === E_ERROR ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors 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 empty(..) or ! empty(...) instead.

Loading history...
368
                $errors['type'] === E_PARSE ||
369
                $errors['type'] === E_CORE_ERROR ||
370
                $errors['type'] === E_COMPILE_ERROR || 
371
                $errors['type'] === E_RECOVERABLE_ERROR ))
372
            {
373
                $error_msg .= $this->get_error_type($errors['type']) . " {$errors['message']} in {$errors['file']} on line {$errors['line']}";
0 ignored issues
show
Bug introduced by
The method get_error_type() does not exist on Worker. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

373
                $error_msg .= $this->/** @scrutinizer ignore-call */ get_error_type($errors['type']) . " {$errors['message']} in {$errors['file']} on line {$errors['line']}";

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
374
            }
375
            log::add($error_msg, 'Error');
376
        }
377
    }
378
379
    /**
380
     * 获取错误类型对应的意义
381
     * @param integer $type
382
     * @return string
383
     */
384
    protected function get_error_type($type)
385
    {
386
        switch($type)
387
        {
388
        case E_ERROR: // 1 //
389
            return 'E_ERROR';
390
        case E_WARNING: // 2 //
391
            return 'E_WARNING';
392
        case E_PARSE: // 4 //
393
            return 'E_PARSE';
394
        case E_NOTICE: // 8 //
395
            return 'E_NOTICE';
396
        case E_CORE_ERROR: // 16 //
397
            return 'E_CORE_ERROR';
398
        case E_CORE_WARNING: // 32 //
399
            return 'E_CORE_WARNING';
400
        case E_COMPILE_ERROR: // 64 //
401
            return 'E_COMPILE_ERROR';
402
        case E_COMPILE_WARNING: // 128 //
403
            return 'E_COMPILE_WARNING';
404
        case E_USER_ERROR: // 256 //
405
            return 'E_USER_ERROR';
406
        case E_USER_WARNING: // 512 //
407
            return 'E_USER_WARNING';
408
        case E_USER_NOTICE: // 1024 //
409
            return 'E_USER_NOTICE';
410
        case E_STRICT: // 2048 //
411
            return 'E_STRICT';
412
        case E_RECOVERABLE_ERROR: // 4096 //
413
            return 'E_RECOVERABLE_ERROR';
414
        case E_DEPRECATED: // 8192 //
415
            return 'E_DEPRECATED';
416
        case E_USER_DEPRECATED: // 16384 //
417
            return 'E_USER_DEPRECATED';
418
        }
419
        return "";
420
    }
421
}
422