GithubHelp home page GithubHelp logo

\Swoole\Server::shutdown never gets called and signals don't seem to work with \Swoole\Process::signal() / pcntl_signal() about swoole-src HOT 4 CLOSED

henrywood avatar henrywood commented on July 18, 2024
\Swoole\Server::shutdown never gets called and signals don't seem to work with \Swoole\Process::signal() / pcntl_signal()

from swoole-src.

Comments (4)

NathanFreeman avatar NathanFreeman commented on July 18, 2024

Forcibly killing the process will not trigger onShutdown, such as kill -9
Use kill -15 to send the SIGTERM signal to the main process in order to terminate according to the normal process flow
Pressing Ctrl+C in the command line will immediately stop the program, and onShutdown will not be called at the lower level

see https://wiki.swoole.com/en/#/server/events?id=onshutdown

from swoole-src.

henrywood avatar henrywood commented on July 18, 2024

I understand, however I signal the master/first process using

kill -QUIT

but but what I then see is that the signal handler is being called (which then converts the signal to SIGTERM) but if you look closely on the output:

Shutting down II ...
XShutting down II ...
XKilled

You may see that the shutdown callback is not getting called even if you send SIGTERM

from swoole-src.

NathanFreeman avatar NathanFreeman commented on July 18, 2024

Certain signals, like SIGTERM and SIGALRM, cannot be set as monitored signals in Swoole\Server

from swoole-src.

henrywood avatar henrywood commented on July 18, 2024

I got it to work like this:

<?php
require __DIR__ . '/vendor/autoload.php';

use Xin\Swoole\Rpc\Server;
use Xin\Cli\Color;
use Xin\Swoole\Rpc\Handler\Handler;
use Xin\Swoole\Rpc\Enum;
use Xin\Swoole\Rpc\LoggerInterface;

/////////////////////////////////////////////////////////////////////////

define('RPCSERVER_RUN_AS_USER', 'henrik');
define('RPCSERVER_PORT', 11520);

/////////////////////////////////////////////////////////////////////////

$workerPID = new Swoole\Atomic(0);

/////////////////////////////////////////////////////////////////////////

function me() : string {
        return basename($_SERVER['argv'][0], (defined('EXT')) ? '.'.EXT : '.php');
}

function logger(string $msg) : void {
        echo $msg.PHP_EOL;
}

/////////////////////////////////////////////////////////////////////////

class RPCMethodDocumentation {

        public function __construct(public string $handlerName, private \ReflectionMethod $method) {}

                public function __toString() {

                        $comment = $this->method->getDocComment();
                        if (empty($comment)) $comment = '/**'.PHP_EOL.' * <NO DESCRIPTION>'.PHP_EOL.' */';
                        $comment = str_replace("\t", "", $comment);
                        $type = $this->method->getReturnType();
                        $type = ($type === NULL) ? 'void' : (string) $type;
                        $out = $comment.PHP_EOL.$this->method->name.'('.$this->renderParams().') : '.$type.PHP_EOL.PHP_EOL;
                        return $out;
                }

        private function renderParams() {

                $out = [];

                foreach($this->method->getParameters() as $refParam) {

                        $aNull = ($refParam->allowsNull()) ? '?' : '';
                        $str = $aNull.$refParam->getType().' $'.$refParam->getName();

                        if ($default = $refParam->getDefaultValue()) {
                                $str.= ' = '.$default;
                        }

                        $out[] = $str;
                }

                return implode(', ', $out);
        }
}

/////////////////////////////////////////////////////////////////////////

function scanForRPCHandlers() : array {

        $out = [];

        $classes = get_declared_classes();

        foreach($classes as $class) {

                $c = new \ReflectionClass($class);

                if ($c->isSubclassOf(Handler::class)) {

                        $handlerName = $class;

                        $methods = $c->getMethods(\ReflectionMethod::IS_PUBLIC);

                        foreach($methods as $m) {

                                if ($m->name == '__construct') continue;

                                if (! isset($out[$handlerName])) {
                                        $out[$handlerName] = [];
                                }

                                $out[$handlerName][$m->name] = new RPCMethodDocumentation($handlerName, $m);
                        }
                }
        }

        return $out;
}

///////////////////////////////////////////////////////////////////////////////////////////

class ElyRPCServer extends \Xin\Swoole\Rpc\Server {

        private static $server;

        public function serve($host, $port, $config = []) {

                if (!extension_loaded('swoole')) {
                        echo Color::error('The swoole extension is not installed');
                        return;
                }

                if (!extension_loaded('signal_handler')) {
                        echo Color::error('The signal_handler extension is not installed');
                        return;
                }

                $this->host = $host;
                $this->port = intval($port);
                $this->config = $config;

                set_time_limit(0);
                $server = new \Swoole\Server($this->host, $this->port, SWOOLE_BASE);

                $server->set($config);

                $server->on('receive', [$this, 'receive']);
                $server->on('workerStart', [$this, 'workerStart']);

                $this->beforeServerStart($server);

                $server->start();
        }

        public static function shutdownServer() : void {

                global $workerPID;
                if (getmypid() === $workerPID->get()) return;

                $pidFile = '/var/run/'.me().'/'.me().'.pid';
                if (file_exists($pidFile)) @unlink($pidFile);

                // Uinregister here with WSSERVER
                /*
                go(function () {

                        try {
                                $p = (int) constant(strtoupper(me()).'_PORT');
                                [$ip, $port] = explode(':', COMMON_WSSERVER_ADDRESS);
                                $client = new HttpClient($ip, $port);
                                $client->set(['timeout' => 3]);
                                $client->upgrade('/');
                                $ipAndPort = '-'.getLocalIP().':'.$p.'='.me().'-';
                                $client->push($ipAndPort);
                                $reply  = $client->recv()->data;
                                logger(sprintf("De-registered at '%s' - Reply was: '%s' ", COMMON_WSSERVER_ADDRESS, $reply));
                                $client->close();
                        } catch(Exception) {}
                });
                 */

                $unixSocket = '/var/run/'.me().'.sock';
                if (file_exists($unixSocket)) @unlink($unixSocket);

                logger('Shutting down ...');
                closelog();
        }

        public function workerStart($server, $workerId) {

                global $workerPID;

                swoole_set_process_name(me().' [rpc] [worker]');

                $workerPID->set(getmypid());
        }

        public function receive($server, $fd, $reactor_id, $data) {

                $data = trim($data);
                if ($this->debug) {
                        echo Color::colorize("fd:{$fd} data:{$data}", Color::FG_LIGHT_GREEN) . PHP_EOL;
                }
                try {
                        $data = json_decode($data, true);
                        $service = $data[Enum::SERVICE];
                        $method = $data[Enum::METHOD];
                        $arguments = $data[Enum::ARGUMENTS];

                        if (!isset($this->services[$service])) {
                                throw new RpcException('The service handler does not exist!');
                        }

                        $ref = new ReflectionClass($this->services[$service]);
                        $handler = $ref->newInstance($server, $fd, $reactor_id);
                        $result = $handler->$method(...$arguments);
                        $response = $this->success($result);
                        $server->send($fd, json_encode($response));

                        if ($this->logger && $this->logger instanceof LoggerInterface) {
                                $this->logger->info($data, $response);
                        }
                } catch (\Exception $ex) {
                        $response = $this->fail($ex->getCode(), $ex->getMessage());
                        $server->send($fd, json_encode($response));

                        if ($this->logger && $this->logger instanceof LoggerInterface) {
                                $this->logger->error($data, $response, $ex);
                        }
                }
        }

        public function beforeServerStart($server) {

                swoole_set_process_name(me().' [rpc] [master]');

                if (posix_isatty(\STDOUT)) {

                        $me = me();
                        echo Color::colorize("-----------------------------------------------------------------------------------", Color::FG_LIGHT_GREEN) . PHP_EOL;
                        echo Color::colorize("          {$me} :: RPC Server - Listening on TCP {$this->port}        ", Color::FG_CYAN , Color::FG_LIGHT_GREEN) . PHP_EOL;
                        echo Color::colorize("-----------------------------------------------------------------------------------", Color::FG_LIGHT_GREEN) . PHP_EOL;
                }
        }
}

class RPCLogger implements LoggerInterface {

        public function info($request, $response) {

                var_dump($request, $response);

                $info = '';

                // TODO
                logger('INFO [ RPC ] :: '.$info);
        }

        public function error($request, $response, Exception $ex) {

                // TODO

        }
}

/////////////////////////////////////////////////////////////////////////////////////////

class ElyRPCHandler extends Handler {

        public function __construct($server, $fd, $reactorId) {
                $this->server = $server;
                $this->fd = $fd;
                $this->reactorId = $reactorId;
        }
}

/////////////////////////////////////////////////////////////////////////////////////////

class TestHandler extends ElyRPCHandler {

        /**
         * Returns 'success' !
         */
        public function test() : string {

                return 'success';
        }

        public function test2(array $foo = []) {

                return array_reverse($foo);
        }

        private function foobar() {

                return 'HELLO';
        }
}

/////////////////////////////////////////////////////////////////////////////////////////

$handlers = scanForRPCHandlers();

//var_dump($handlers);

foreach($handlers as $handler => $methods) {

        echo $handler.PHP_EOL;
        echo "------------------------------------".PHP_EOL;

        foreach($methods as $method) {
                echo (string) $method.PHP_EOL;
        }
}

if (posix_getuid() !== 0) {
        echo "Error: Must be started as root\n";
        die(1);
}

$daemonize = TRUE;
$rpcDebug = FALSE;
$rpcService = 'test';
$rpcServiceClass = TestHandler::class;

$server = new ElyRPCServer();
$server->logger = new \RPCLogger();
$server->setDebug($rpcDebug);
$server->setHandler($rpcService, $rpcServiceClass);

if (! $daemonize) {

        attach_signal(SIGINT, function() {

                ElyRPCServer::shutdownServer();
                sleep(1);
                posix_kill(getmypid(), SIGKILL);
        });

} else {

        attach_signal(SIGTERM, function() {

                global $workerPID;
                ElyRPCServer::shutdownServer();
                sleep(1);
                posix_kill($workerPID->get(), SIGKILL);
                posix_kill(getmypid(), SIGKILL);

        });

        attach_signal(SIGQUIT, function() {

                global $workerPID;
                ElyRPCServer::shutdownServer();
                sleep(1);
                posix_kill($workerPID->get(), SIGKILL);
                posix_kill(getmypid(), SIGKILL);
        });
}

$userInfo=posix_getpwnam(constant(strtoupper(me()).'_RUN_AS_USER'));
$userID=$userInfo["uid"];

$pidFile = '/var/run/'.me().'/'.me().'.pid';

@mkdir(dirname($pidFile), 0777);
chmod(dirname($pidFile), 0777);
chown(dirname($pidFile), $userID);
touch($pidFile);
chmod($pidFile, 0777);
chown($pidFile, $userID);

// TODO: Log file


// Become the proper user
posix_setuid($userID);

$port = (int) constant(strtoupper(me()).'_PORT');

// Start server
$server->serve('0.0.0.0', $port, [
        'log_file'			=> 	$logFile,
        'pid_file'			=>      $pidFile,
        'daemonize'			=>      $daemonize,
        'max_request'                   =>      500,            // Total number of requests to be processed
        'open_eof_check'                =>      TRUE,
        'package_eof'                   =>      "\r\n",
]);

from swoole-src.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.