Closed henrywood closed 2 weeks ago
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
I understand, however I signal the master/first process using
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
Certain signals, like SIGTERM and SIGALRM, cannot be set as monitored signals in Swoole\Server
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",
]);
Please answer these questions before submitting your issue.
I have this code
I tried using both pcntl_signal and \Swoole\Process::signal() but neither of them would work. I therefore use attach_signal from https://github.com/rstgroup/php-signal-handler (PHP Extension)
I expected $server->shutdown() to actually call ElyRPCServer::shutdown() and signals to be processed correctly.
ElyRPCServer::shutdown() never gets called (file /tmp/foo5 is never written)
When pressing CTRL+C, this is the output:
^CShutting down II ... XShutting down II ... XKilled
As it can be seen, none of the echo of ElyRPCServer::shutdown() function is output
NOTE: The Xin\Swoole\Rpc comes from this github repo: https://github.com/limingxinleo/x-swoole-rpc/tree/master
How can I make it work correctly ?
php --ri swoole
)?swoole
Swoole => enabled Author => Swoole Team team@swoole.com Version => 5.1.0-dev Built => Jul 8 2023 19:03:10 coroutine => enabled with boost asm context epoll => enabled eventfd => enabled signalfd => enabled cpu_affinity => enabled spinlock => enabled rwlock => enabled openssl => OpenSSL 1.1.1f 31 Mar 2020 dtls => enabled http2 => enabled json => enabled pcre => enabled zlib => 1.2.11 mutex_timedlock => enabled pthread_barrier => enabled futex => enabled async_redis => enabled
Directive => Local Value => Master Value swoole.enable_coroutine => On => On swoole.enable_library => On => On swoole.enable_fiber_mock => Off => Off swoole.enable_preemptive_scheduler => Off => Off swoole.display_errors => On => On swoole.use_shortname => On => On swoole.unixsock_buffer_size => 8388608 => 8388608
uname -a
&php -v
&gcc -v
) ?Linux HSLAPTOP-ASUS 5.10.102.1-microsoft-standard-WSL2 #1 SMP Wed Mar 2 00:30:59 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux PHP 8.2.10 (cli) (built: Sep 2 2023 06:58:59) (NTS) Copyright (c) The PHP Group Zend Engine v4.2.10, Copyright (c) Zend Technologies Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none:hsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.2' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-9QDOt0/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)