Skip to content
This repository has been archived by the owner on Jan 17, 2022. It is now read-only.

Commit

Permalink
feat(server): handle signals to enable graceful termination
Browse files Browse the repository at this point in the history
  • Loading branch information
k911 committed Feb 2, 2021
1 parent 807ba9f commit 4cc5995
Show file tree
Hide file tree
Showing 49 changed files with 1,291 additions and 410 deletions.
626 changes: 315 additions & 311 deletions composer.lock

Large diffs are not rendered by default.

40 changes: 23 additions & 17 deletions src/Bridge/Symfony/Bundle/Command/AbstractServerStartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use function K911\Swoole\decode_string_as_set;
use function K911\Swoole\format_bytes;
use function K911\Swoole\get_max_memory;
use K911\Swoole\Process\Signal\PcntlSignalHandler;
use K911\Swoole\Process\Signal\Signal;
use K911\Swoole\Server\Config\Socket;
use K911\Swoole\Server\Configurator\ConfiguratorInterface;
use K911\Swoole\Server\HttpServer;
Expand All @@ -25,33 +27,28 @@

abstract class AbstractServerStartCommand extends Command
{
/**
* @var ParameterBagInterface
*/
protected $parameterBag;

private $server;
private $bootManager;
private $serverConfiguration;
private $serverConfigurator;

/**
* @var bool
*/
private $testing = false;
protected ParameterBagInterface $parameterBag;
private HttpServer $server;
private BootableInterface $bootManager;
private HttpServerConfiguration $serverConfiguration;
private ConfiguratorInterface $serverConfigurator;
private ?PcntlSignalHandler $pcntlSignalHandler;
private bool $testing = false;

public function __construct(
HttpServer $server,
HttpServerConfiguration $serverConfiguration,
ConfiguratorInterface $serverConfigurator,
ParameterBagInterface $parameterBag,
BootableInterface $bootManager
BootableInterface $bootManager,
?PcntlSignalHandler $pcntlSignalHandler
) {
$this->server = $server;
$this->bootManager = $bootManager;
$this->parameterBag = $parameterBag;
$this->serverConfigurator = $serverConfigurator;
$this->serverConfiguration = $serverConfiguration;
$this->pcntlSignalHandler = $pcntlSignalHandler;

parent::__construct();
}
Expand Down Expand Up @@ -160,7 +157,7 @@ protected function prepareServerConfiguration(HttpServerConfiguration $serverCon
$sockets->changeApiSocket(new Socket('0.0.0.0', (int) $apiPort));
}

if (\filter_var($input->getOption('serve-static'), FILTER_VALIDATE_BOOLEAN)) {
if (\filter_var($input->getOption('serve-static'), \FILTER_VALIDATE_BOOLEAN)) {
$publicDir = $input->getOption('public-dir');
Assertion::string($publicDir, 'Public dir must be a valid path');
$serverConfiguration->enableServingStaticFiles($publicDir);
Expand Down Expand Up @@ -201,7 +198,7 @@ protected function prepareConfigurationRowsToPrint(HttpServerConfiguration $serv
['worker_count', $serverConfiguration->getWorkerCount()],
['reactor_count', $serverConfiguration->getReactorCount()],
['worker_max_request', $serverConfiguration->getMaxRequest()],
['worker_max_request_grace', $serverConfiguration->getMaxRequestGrace()],
['worker_max_request_grace', $serverConfiguration->getMaxRequestGrace() ?? '~'],
['memory_limit', format_bytes(get_max_memory())],
['trusted_hosts', \implode(', ', $runtimeConfiguration['trustedHosts'])],
];
Expand All @@ -226,6 +223,15 @@ protected function startServer(HttpServerConfiguration $serverConfiguration, Htt
{
$io->comment('Quit the server with CONTROL-C.');

if ($this->pcntlSignalHandler instanceof PcntlSignalHandler && $serverConfiguration->isReactorRunningMode()) {
// Register dummy SIGINT handler to make sure process doesn't exit to early when CONTROL-C is hit
$this->pcntlSignalHandler->register(function () use ($server): void {
if (\getmypid() !== $server->getMasterPid()) {
$server->shutdown();
}
}, Signal::int());
}

if ($server->start()) {
$io->newLine();
$io->success('Swoole HTTP Server has been successfully shutdown.');
Expand Down
2 changes: 1 addition & 1 deletion src/Bridge/Symfony/Bundle/Command/ServerStartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private function closeConsoleOutput(ConsoleOutput $output): void

private function closeStreamOutput(StreamOutput $output): void
{
$output->setVerbosity(PHP_INT_MIN);
$output->setVerbosity(\PHP_INT_MIN);
\fclose($output->getStream());
}
}
2 changes: 1 addition & 1 deletion src/Bridge/Symfony/Bundle/Command/ServerStatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private function showStatus(SymfonyStyle $io, array $status): void

private function showMetrics(SymfonyStyle $io, array $metrics): void
{
$date = \DateTimeImmutable::createFromFormat(DATE_ATOM, $metrics['date']);
$date = \DateTimeImmutable::createFromFormat(\DATE_ATOM, $metrics['date']);
Assertion::isInstanceOf($date, \DateTimeImmutable::class);
$server = $metrics['server'];
$runningSeconds = $date->getTimestamp() - $server['start_time'];
Expand Down
19 changes: 19 additions & 0 deletions src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,18 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end() // drivers
->arrayNode('coroutine')
->addDefaultsIfNotSet()
->canBeDisabled()
->children()
->arrayNode('hooks')
->defaultValue(['all'])
->prototype('enum')
->values(['off', 'all'])
->end()
->end()
->end()
->end()
->arrayNode('settings')
->addDefaultsIfNotSet()
->children()
Expand Down Expand Up @@ -240,6 +252,13 @@ public function getConfigTreeBuilder(): TreeBuilder
->min(0)
->defaultValue(0)
->end()
->booleanNode('worker_reload_async')
->defaultTrue()
->end()
->integerNode('worker_exit_timeout_seconds')
->min(0)
->defaultValue(5)
->end()
->scalarNode('worker_max_request_grace')
->defaultNull()
->end()
Expand Down
38 changes: 38 additions & 0 deletions src/Bridge/Symfony/Bundle/DependencyInjection/SwooleExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
use K911\Swoole\Bridge\Symfony\Messenger\SwooleServerTaskTransportFactory;
use K911\Swoole\Bridge\Symfony\Messenger\SwooleServerTaskTransportHandler;
use K911\Swoole\Bridge\Upscale\Blackfire\WithProfiler;
use K911\Swoole\Process\Signal\PcntlSignalHandler;
use K911\Swoole\Server\Config\Socket;
use K911\Swoole\Server\Config\Sockets;
use K911\Swoole\Server\Configurator\ConfiguratorInterface;
use K911\Swoole\Server\Configurator\WithProcess;
use K911\Swoole\Server\HttpServer;
use K911\Swoole\Server\HttpServerConfiguration;
use K911\Swoole\Server\RequestHandler\AdvancedStaticFilesServer;
Expand All @@ -32,10 +34,13 @@
use K911\Swoole\Server\Runtime\HMR\HotModuleReloaderInterface;
use K911\Swoole\Server\Runtime\HMR\InotifyHMR;
use K911\Swoole\Server\TaskHandler\TaskHandlerInterface;
use K911\Swoole\Server\WorkerHandler\ClearAllTimersWorkerExitHandler;
use K911\Swoole\Server\WorkerHandler\HMRWorkerStartHandler;
use K911\Swoole\Server\WorkerHandler\WorkerExitHandlerInterface;
use K911\Swoole\Server\WorkerHandler\WorkerStartHandlerInterface;
use ReflectionMethod;
use RuntimeException;
use Swoole\Process;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
Expand Down Expand Up @@ -192,6 +197,7 @@ private function registerHttpServerConfiguration(array $config, ContainerBuilder
'socket_type' => $socketType,
'ssl_enabled' => $sslEnabled,
'settings' => $settings,
'coroutine' => $coroutine,
] = $config;

if ('auto' === $static['strategy']) {
Expand All @@ -211,6 +217,8 @@ private function registerHttpServerConfiguration(array $config, ContainerBuilder

$settings['serve_static'] = $static['strategy'];
$settings['public_dir'] = $static['public_dir'];
$settings['coroutine_enabled'] = $coroutine['enabled'];
$settings['coroutine_hooks'] = $coroutine['hooks'];

if ('auto' === $settings['log_level']) {
$settings['log_level'] = $this->isDebug($container) ? 'debug' : 'notice';
Expand All @@ -234,9 +242,32 @@ private function registerHttpServerConfiguration(array $config, ContainerBuilder
->addArgument($settings)
;

$this->registerSignalHandlers($runningMode, $container);
$this->registerHttpServerHMR($hmr, $container);
}

private function registerSignalHandlers(string $runningMode, ContainerBuilder $container): void
{
if ('reactor' !== $runningMode) {
return;
}

if (\extension_loaded('pcntl') && \extension_loaded('posix')) {
$container->register(PcntlSignalHandler::class);
}

$container->register('swoole_bundle.server.http_server.configurator.with_process_signal_shutdown_handler')
->setClass(WithProcess::class)
->setAutowired(false)
->setAutoconfigured(false)
->setPublic(false)
->setArguments([Process::class => new Reference('swoole_bundle.http_server.runtime.server_shutdown.signal_handler_swoole_process')])
;

$def = $container->getDefinition('swoole_bundle.server.http_server.configurator.for_server_run_command');
$def->addArgument(new Reference('swoole_bundle.server.http_server.configurator.with_process_signal_shutdown_handler'));
}

private function registerHttpServerHMR(string $hmr, ContainerBuilder $container): void
{
if ('off' === $hmr || !$this->isDebug($container)) {
Expand All @@ -255,6 +286,13 @@ private function registerHttpServerHMR(string $hmr, ContainerBuilder $container)
->setArgument('$decorated', new Reference(HMRWorkerStartHandler::class.'.inner'))
->setDecoratedService(WorkerStartHandlerInterface::class)
;

$container->autowire(ClearAllTimersWorkerExitHandler::class)
->setPublic(false)
->setAutoconfigured(true)
->setArgument('$decorated', new Reference(ClearAllTimersWorkerExitHandler::class.'.inner'))
->setDecoratedService(WorkerExitHandlerInterface::class)
;
}

private function resolveAutoHMR(): string
Expand Down
33 changes: 31 additions & 2 deletions src/Bridge/Symfony/Bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,31 @@ services:

K911\Swoole\Server\RequestHandler\LimitedRequestHandler:

K911\Swoole\Server\LifecycleHandler\SigIntHandler:
K911\Swoole\Server\Runtime\ServerShutdown\SignalServerShutdownHandlerOnServerStart:

K911\Swoole\Component\Clock\ClockInterface:
class: K911\Swoole\Component\Clock\CoroutineFriendlyClock

K911\Swoole\Process\Signal\SwooleProcessSignalHandler:

K911\Swoole\Process\ProcessFactory:

K911\Swoole\Process\ProcessManagerInterface:
class: K911\Swoole\Process\ProcessManager

K911\Swoole\Process\Signal\SignalHandlerInterface:
alias: K911\Swoole\Process\Signal\SwooleProcessSignalHandler

swoole_bundle.http_server.runtime.server_shutdown.signal_handler_process:
class: K911\Swoole\Server\Runtime\ServerShutdown\SignalServerShutdownHandlerAsServerProcess

swoole_bundle.http_server.runtime.server_shutdown.signal_handler_swoole_process:
class: Swoole\Process
factory: ['@K911\Swoole\Process\ProcessFactory', make]
autowire: false
autoconfigure: false
arguments:
$process: '@swoole_bundle.http_server.runtime.server_shutdown.signal_handler_process'

K911\Swoole\Server\Runtime\CallableBootManagerFactory:

Expand All @@ -55,6 +79,9 @@ services:
K911\Swoole\Server\WorkerHandler\WorkerStartHandlerInterface:
class: K911\Swoole\Server\WorkerHandler\NoOpWorkerStartHandler

K911\Swoole\Server\WorkerHandler\WorkerExitHandlerInterface:
class: K911\Swoole\Server\WorkerHandler\NoOpWorkerExitHandler

K911\Swoole\Server\LifecycleHandler\ServerStartHandlerInterface:
class: K911\Swoole\Server\LifecycleHandler\NoOpServerStartHandler

Expand Down Expand Up @@ -102,6 +129,8 @@ services:

K911\Swoole\Server\Configurator\WithWorkerStartHandler:

K911\Swoole\Server\Configurator\WithWorkerExitHandler:

K911\Swoole\Server\Configurator\WithTaskHandler:

K911\Swoole\Server\Configurator\WithTaskFinishedHandler:
Expand Down Expand Up @@ -143,4 +172,4 @@ services:
class: K911\Swoole\Server\Configurator\WithServerStartHandler
autoconfigure: false
arguments:
$handler: '@K911\Swoole\Server\LifecycleHandler\SigIntHandler'
$handler: '@K911\Swoole\Server\Runtime\ServerShutdown\SignalServerShutdownHandlerOnServerStart'
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function newExceptionHandler(Request $request): callable
$e = new ErrorException(
$e->getMessage(),
$e->getCode(),
E_ERROR,
\E_ERROR,
$e->getFile(),
$e->getLine(),
$e->getPrevious()
Expand Down
2 changes: 1 addition & 1 deletion src/Bridge/Symfony/HttpFoundation/RequestFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class RequestFactory implements RequestFactoryInterface
*/
public function make(SwooleRequest $request): HttpFoundationRequest
{
$server = \array_change_key_case($request->server, CASE_UPPER);
$server = \array_change_key_case($request->server, \CASE_UPPER);

// Add formatted headers to server
foreach ($request->header as $key => $value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public function save(): void

$this->storage->set(
$this->currentId,
\json_encode($this->data, JSON_THROW_ON_ERROR),
\json_encode($this->data, \JSON_THROW_ON_ERROR),
$this->sessionLifetimeSeconds
);
}
Expand Down Expand Up @@ -251,7 +251,7 @@ private function obtainSessionData(string $sessionId): array

Assertion::string($sessionData);

return \json_decode($sessionData, true, 512, JSON_THROW_ON_ERROR);
return \json_decode($sessionData, true, 512, \JSON_THROW_ON_ERROR);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/Client/HttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public function serialize(): string
'port' => $this->client->port,
'ssl' => $this->client->ssl,
'options' => $this->client->setting,
], JSON_THROW_ON_ERROR);
], \JSON_THROW_ON_ERROR);
}

/**
Expand All @@ -136,7 +136,7 @@ public function serialize(): string
*/
public function unserialize($serialized): void
{
$spec = \json_decode($serialized, true, 512, JSON_THROW_ON_ERROR);
$spec = \json_decode($serialized, true, 512, \JSON_THROW_ON_ERROR);
$this->client = self::makeSwooleClient($spec['host'], $spec['port'], $spec['ssl'], $spec['options']);
}

Expand Down Expand Up @@ -167,7 +167,7 @@ private function assertHttpMethodSupported(string $method): void
*/
private function serializeRequestData(Client $client, $data): void
{
$json = \json_encode($data, JSON_THROW_ON_ERROR);
$json = \json_encode($data, \JSON_THROW_ON_ERROR);
$client->requestHeaders[Http::HEADER_CONTENT_TYPE] = Http::CONTENT_TYPE_APPLICATION_JSON;
$client->setData($json);
}
Expand Down Expand Up @@ -229,7 +229,7 @@ private function resolveResponseBody(Client $client)

switch ($contentType) {
case Http::CONTENT_TYPE_APPLICATION_JSON:
return \json_decode($client->body, true, 512, JSON_THROW_ON_ERROR);
return \json_decode($client->body, true, 512, \JSON_THROW_ON_ERROR);
case Http::CONTENT_TYPE_TEXT_PLAIN:
case Http::CONTENT_TYPE_TEXT_HTML:
return $client->body;
Expand Down
8 changes: 4 additions & 4 deletions src/Common/XdebugHandler/XdebugHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function allowXdebugEnvName(): string

public function prepareRestartedProcess(): Process
{
$command = [PHP_BINARY, '-n', '-c', $this->createPreparedTempIniFile()];
$command = [\PHP_BINARY, '-n', '-c', $this->createPreparedTempIniFile()];
$currentCommand = $_SERVER['argv'];
$command = \array_merge($command, $currentCommand);

Expand Down Expand Up @@ -139,7 +139,7 @@ private function parsePhpIniContent(iterable $iniFiles): string
}

$data = \preg_replace($regex, ';$1', $iniContent);
$content .= $data.PHP_EOL;
$content .= $data.\PHP_EOL;
}

// Merge loaded settings into our ini content, if it is valid
Expand All @@ -152,7 +152,7 @@ private function parsePhpIniContent(iterable $iniFiles): string
}

// Work-around for https://bugs.php.net/bug.php?id=75932
$content .= 'opcache.enable_cli=0'.PHP_EOL;
$content .= 'opcache.enable_cli=0'.\PHP_EOL;

return $content;
}
Expand All @@ -175,7 +175,7 @@ private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): strin

if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) {
// Double-quote escape each value
$content .= $name.'="'.\addcslashes($value, '\\"').'"'.PHP_EOL;
$content .= $name.'="'.\addcslashes($value, '\\"').'"'.\PHP_EOL;
}
}

Expand Down
Loading

0 comments on commit 4cc5995

Please sign in to comment.