Completed
Push — master ( f89fcb...8019b6 )
by Biao
03:13
created

Portal::stop()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 37
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 28
nc 13
nop 0
dl 0
loc 37
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
namespace Hhxsv5\LaravelS\Console;
4
5
use Hhxsv5\LaravelS\LaravelS;
6
use Hhxsv5\LaravelS\Swoole\Traits\LogTrait;
7
use Swoole\Process;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputArgument;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Symfony\Component\Console\Style\SymfonyStyle;
14
15
class Portal extends Command
16
{
17
    use LogTrait;
18
19
    protected $basePath;
20
21
    /**
22
     * @var InputInterface $input
23
     */
24
    protected $input;
25
26
    /**
27
     * @var OutputInterface $output
28
     */
29
    protected $output;
30
31
    public function __construct($basePath)
32
    {
33
        parent::__construct('laravels');
34
        $this->basePath = $basePath;
35
    }
36
37
    protected function configure()
38
    {
39
        $this->setDescription('LaravelS console tool');
40
        $this->setHelp('LaravelS console tool');
41
42
        $this->addArgument('action', InputArgument::OPTIONAL, 'start|stop|restart|reload|info|help', 'help');
43
        $this->addOption('env', 'e', InputOption::VALUE_OPTIONAL, 'The environment the command should run under, this feature requires Laravel 5.2+');
44
        $this->addOption('daemonize', 'd', InputOption::VALUE_NONE, 'Whether run as a daemon for "start & restart"');
45
        $this->addOption('ignore', 'i', InputOption::VALUE_NONE, 'Whether ignore checking process pid for "start & restart"');
46
    }
47
48
    protected function execute(InputInterface $input, OutputInterface $output)
49
    {
50
        $this->input = $input;
51
        $this->output = $output;
52
        LaravelS::setOutputStyle(new SymfonyStyle($this->input, $this->output));
53
54
        try {
55
            $action = $input->getArgument('action');
56
            switch ($action) {
57
                case 'start':
58
                    $this->start();
59
                    break;
60
                case 'stop':
61
                    $this->stop();
62
                    break;
63
                case 'restart':
64
                    $this->restart();
65
                    break;
66
                case 'reload':
67
                    $this->reload();
68
                    break;
69
                case 'info':
70
                    $this->showInfo();
71
                    break;
72
                default:
73
                    $help = <<<EOS
74
75
Usage: 
76
  [%s] ./bin/laravels [options] <action>
77
78
Arguments:
79
  action                start|stop|restart|reload|info|help
80
81
Options:
82
  -e, --env             The environment the command should run under, this feature requires Laravel 5.2+
83
  -d, --daemonize       Whether run as a daemon for "start & restart"
84
  -i, --ignore          Whether ignore checking process pid for "start & restart"
85
EOS;
86
87
                    $this->info(sprintf($help, PHP_BINARY));
88
                    break;
89
            }
90
        } catch (\Exception $e) {
91
            $error = sprintf(
92
                'Uncaught exception "%s"([%d]%s) at %s:%s, %s%s',
93
                get_class($e),
94
                $e->getCode(),
95
                $e->getMessage(),
96
                $e->getFile(),
97
                $e->getLine(),
98
                PHP_EOL,
99
                $e->getTraceAsString()
100
            );
101
            $this->error($error);
102
        }
103
    }
104
105
    public function start()
106
    {
107
        // Initialize configuration config/laravels.json
108
        $options = $this->input->getOptions();
109
        unset($options['env']);
110
        $options = array_filter($options);
111
        $optionStr = '';
112
        foreach ($options as $key => $value) {
113
            $optionStr .= sprintf('--%s%s ', $key, is_bool($value) ? '' : ('=' . $value));
114
        }
115
        $this->runArtisanCommand(trim('laravels config ' . $optionStr));
116
117
        // Here we go...
118
        $config = $this->getConfig();
119
120
        if (!$config['server']['ignore_check_pid'] && file_exists($config['server']['swoole']['pid_file'])) {
121
            $pid = (int)file_get_contents($config['server']['swoole']['pid_file']);
122
            if ($pid > 0 && self::kill($pid, 0)) {
123
                $this->warning(sprintf('Swoole[PID=%d] is already running.', $pid));
124
                return 1;
125
            }
126
        }
127
128
        if ($config['server']['swoole']['daemonize']) {
129
            $this->trace('Swoole is running in daemon mode, see "ps -ef|grep laravels".');
130
        } else {
131
            $this->trace('Swoole is running, press Ctrl+C to quit.');
132
        }
133
134
        (new LaravelS($config['server'], $config['laravel']))->run();
135
136
        return 0;
137
    }
138
139
    public function stop()
140
    {
141
        $config = $this->getConfig();
142
        $pidFile = $config['server']['swoole']['pid_file'];
143
        if (!file_exists($pidFile)) {
144
            $this->warning('It seems that Swoole is not running.');
145
            return 0;
146
        }
147
148
        $pid = file_get_contents($pidFile);
149
        if (self::kill($pid, 0)) {
150
            if (self::kill($pid, SIGTERM)) {
151
                // Make sure that master process quit
152
                $time = 1;
153
                $waitTime = isset($config['server']['swoole']['max_wait_time']) ? $config['server']['swoole']['max_wait_time'] : 60;
154
                $this->info("The max time of waiting to forcibly stop is {$waitTime}s.");
155
                while (self::kill($pid, 0)) {
156
                    if ($time > $waitTime) {
157
                        $this->warning("Swoole [PID={$pid}] cannot be stopped gracefully in {$waitTime}s, will be stopped forced right now.");
158
                        return 1;
159
                    }
160
                    $this->info("Waiting Swoole[PID={$pid}] to stop. [{$time}]");
161
                    sleep(1);
162
                    $time++;
163
                }
164
                if (file_exists($pidFile)) {
165
                    unlink($pidFile);
166
                }
167
                $this->info("Swoole [PID={$pid}] is stopped.");
168
                return 0;
169
            } else {
170
                $this->error("Swoole [PID={$pid}] is stopped failed.");
171
                return 1;
172
            }
173
        } else {
174
            $this->warning("Swoole [PID={$pid}] does not exist, or permission denied.");
175
            return 0;
176
        }
177
    }
178
179
    public function restart()
180
    {
181
        $code = $this->stop();
182
        if ($code !== 0) {
183
            return $code;
184
        }
185
        return $this->start();
186
    }
187
188
    public function reload()
189
    {
190
        $config = $this->getConfig();
191
        $pidFile = $config['server']['swoole']['pid_file'];
192
        if (!file_exists($pidFile)) {
193
            $this->error('It seems that Swoole is not running.');
194
            return;
195
        }
196
197
        // Reload worker process
198
        $pid = file_get_contents($pidFile);
199
        if (!$pid || !self::kill($pid, 0)) {
200
            $this->error("Swoole [PID={$pid}] does not exist, or permission denied.");
201
            return;
202
        }
203
204
        if (self::kill($pid, SIGUSR1)) {
205
            $now = date('Y-m-d H:i:s');
206
            $this->info("Swoole [PID={$pid}] is reloaded at {$now}.");
207
        } else {
208
            $this->error("Swoole [PID={$pid}] is reloaded failed.");
209
        }
210
211
        // Reload timer process
212
        $pidFile = $config['server']['timer']['pid_file'];
213
        $pid = file_get_contents($pidFile);
214
        if (!$pid || !self::kill($pid, 0)) {
215
            $this->error("Swoole Timer [PID={$pid}] does not exist, or permission denied.");
216
            return;
217
        }
218
219
        if (self::kill($pid, SIGUSR1)) {
220
            $now = date('Y-m-d H:i:s');
221
            $this->info("Swoole Timer [PID={$pid}] is reloaded at {$now}.");
222
        } else {
223
            $this->error("Swoole Timer [PID={$pid}] is reloaded failed.");
224
        }
225
    }
226
227
    public function showInfo()
228
    {
229
        $this->runArtisanCommand('laravels info');
230
    }
231
232
    public function artisanCmd($subCmd)
233
    {
234
        $phpCmd = sprintf('%s -c "%s"', PHP_BINARY, php_ini_loaded_file());
235
        $env = $this->input->getOption('env');
236
        $envs = $env ? "APP_ENV={$env}" : '';
237
        $artisanCmd = trim(sprintf('%s %s %s/artisan %s', $envs, $phpCmd, $this->basePath, $subCmd));
238
        return $artisanCmd;
239
    }
240
241
    public function runArtisanCommand($cmd)
242
    {
243
        $cmd = $this->artisanCmd($cmd);
244
        self::runCommand($cmd);
245
    }
246
247
    public function getConfig()
248
    {
249
        $json = file_get_contents($this->basePath . '/storage/laravels.json');
250
        return (array)json_decode($json, true);
251
    }
252
253
    public static function runCommand($cmd, $input = null)
254
    {
255
        $fp = popen($cmd, 'w');
256
        if ($fp === false) {
257
            return false;
258
        }
259
        if ($input !== null) {
260
            fwrite($fp, $input);
261
        }
262
        pclose($fp);
263
        return true;
264
    }
265
266
    public static function kill($pid, $sig)
267
    {
268
        try {
269
            return Process::kill($pid, $sig);
270
        } catch (\Exception $e) {
271
            return false;
272
        }
273
    }
274
}
275