From 7e4be29fc44c2a7298b5b803c3f805be940c3439 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 2 Nov 2021 13:51:01 +0000 Subject: [PATCH] Gracefully force-shutdown on failure to start RakLib this now won't generate a crashdump. --- resources/locale | 2 +- src/Server.php | 15 ++++- src/lang/KnownTranslationFactory.php | 8 +++ src/lang/KnownTranslationKeys.php | 1 + src/network/Network.php | 3 + src/network/NetworkInterface.php | 1 + .../NetworkInterfaceStartException.php | 32 ++++++++++ src/network/mcpe/raklib/RakLibInterface.php | 11 +++- src/network/mcpe/raklib/RakLibServer.php | 30 ++++++--- .../mcpe/raklib/RakLibThreadCrashInfo.php | 61 +++++++++++++++++++ 10 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 src/network/NetworkInterfaceStartException.php create mode 100644 src/network/mcpe/raklib/RakLibThreadCrashInfo.php diff --git a/resources/locale b/resources/locale index 09c709f24..f9076e4a6 160000 --- a/resources/locale +++ b/resources/locale @@ -1 +1 @@ -Subproject commit 09c709f2426cb8d21d2a4851ad6df5a254a40fd4 +Subproject commit f9076e4a6e3049aeaf7abd30d39a482a1392eafe diff --git a/src/Server.php b/src/Server.php index 475a4385b..277a56ad6 100644 --- a/src/Server.php +++ b/src/Server.php @@ -64,6 +64,7 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\raklib\RakLibInterface; use pocketmine\network\Network; +use pocketmine\network\NetworkInterfaceStartException; use pocketmine\network\query\DedicatedQueryNetworkInterface; use pocketmine\network\query\QueryHandler; use pocketmine\network\query\QueryInfo; @@ -980,6 +981,7 @@ class Server{ $this->enablePlugins(PluginEnableOrder::POSTWORLD()); if(!$this->startupPrepareNetworkInterfaces()){ + $this->forceShutdown(); return; } @@ -1113,7 +1115,18 @@ class Server{ private function startupPrepareNetworkInterfaces() : bool{ $useQuery = $this->configGroup->getConfigBool("enable-query", true); - if(!$this->network->registerInterface(new RakLibInterface($this)) && $useQuery){ + + try{ + $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this)); + }catch(NetworkInterfaceStartException $e){ + $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed( + $this->getIp(), + (string) $this->getPort(), + $e->getMessage() + ))); + return false; + } + if(!$rakLibRegistered && $useQuery){ //RakLib would normally handle the transport for Query packets //if it's not registered we need to make sure Query still works $this->network->registerInterface(new DedicatedQueryNetworkInterface($this->getIp(), $this->getPort(), new \PrefixedLogger($this->logger, "Dedicated Query Interface"))); diff --git a/src/lang/KnownTranslationFactory.php b/src/lang/KnownTranslationFactory.php index a08e87f5c..04b644f83 100644 --- a/src/lang/KnownTranslationFactory.php +++ b/src/lang/KnownTranslationFactory.php @@ -1950,6 +1950,14 @@ final class KnownTranslationFactory{ ]); } + public static function pocketmine_server_networkStartFailed(Translatable|string $ipAddress, Translatable|string $port, Translatable|string $errorMessage) : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_NETWORKSTARTFAILED, [ + "ipAddress" => $ipAddress, + "port" => $port, + "errorMessage" => $errorMessage, + ]); + } + public static function pocketmine_server_query_running(Translatable|string $param0, Translatable|string $param1) : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_QUERY_RUNNING, [ 0 => $param0, diff --git a/src/lang/KnownTranslationKeys.php b/src/lang/KnownTranslationKeys.php index cf4aca555..2a609c8d8 100644 --- a/src/lang/KnownTranslationKeys.php +++ b/src/lang/KnownTranslationKeys.php @@ -401,6 +401,7 @@ final class KnownTranslationKeys{ public const POCKETMINE_SERVER_INFO_EXTENDED = "pocketmine.server.info.extended"; public const POCKETMINE_SERVER_LICENSE = "pocketmine.server.license"; public const POCKETMINE_SERVER_NETWORKSTART = "pocketmine.server.networkStart"; + public const POCKETMINE_SERVER_NETWORKSTARTFAILED = "pocketmine.server.networkStartFailed"; public const POCKETMINE_SERVER_QUERY_RUNNING = "pocketmine.server.query.running"; public const POCKETMINE_SERVER_START = "pocketmine.server.start"; public const POCKETMINE_SERVER_STARTFINISHED = "pocketmine.server.startFinished"; diff --git a/src/network/Network.php b/src/network/Network.php index 22c1b2f39..fc65e3aca 100644 --- a/src/network/Network.php +++ b/src/network/Network.php @@ -91,6 +91,9 @@ class Network{ $this->sessionManager->tick(); } + /** + * @throws NetworkInterfaceStartException + */ public function registerInterface(NetworkInterface $interface) : bool{ $ev = new NetworkInterfaceRegisterEvent($interface); $ev->call(); diff --git a/src/network/NetworkInterface.php b/src/network/NetworkInterface.php index 146a7351c..a6beddbae 100644 --- a/src/network/NetworkInterface.php +++ b/src/network/NetworkInterface.php @@ -33,6 +33,7 @@ interface NetworkInterface{ /** * Performs actions needed to start the interface after it is registered. + * @throws NetworkInterfaceStartException */ public function start() : void; diff --git a/src/network/NetworkInterfaceStartException.php b/src/network/NetworkInterfaceStartException.php new file mode 100644 index 000000000..3dfd2b0a7 --- /dev/null +++ b/src/network/NetworkInterfaceStartException.php @@ -0,0 +1,32 @@ +eventReceiver->handle($this)); }); $this->server->getLogger()->debug("Waiting for RakLib to start..."); - $this->rakLib->startAndWait(); + try{ + $this->rakLib->startAndWait(); + }catch(SocketException $e){ + throw new NetworkInterfaceStartException($e->getMessage(), 0, $e); + } $this->server->getLogger()->debug("RakLib booted successfully"); } @@ -132,7 +139,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ if(!$this->rakLib->isRunning()){ $e = $this->rakLib->getCrashInfo(); if($e !== null){ - throw new \RuntimeException("RakLib crashed: $e"); + throw new \RuntimeException("RakLib crashed: " . $e->makePrettyMessage()); } throw new \Exception("RakLib Thread crashed without crash information"); } diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index 7591475ee..94ea2c973 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\raklib; use pocketmine\snooze\SleeperNotifier; use pocketmine\thread\Thread; use raklib\generic\Socket; +use raklib\generic\SocketException; use raklib\server\ipc\RakLibToUserThreadMessageSender; use raklib\server\ipc\UserToRakLibThreadMessageReceiver; use raklib\server\Server; @@ -68,7 +69,7 @@ class RakLibServer extends Thread{ /** @var SleeperNotifier */ protected $mainThreadNotifier; - /** @var string|null */ + /** @var RakLibThreadCrashInfo|null */ public $crashInfo = null; public function __construct( @@ -102,24 +103,24 @@ class RakLibServer extends Thread{ * @return void */ public function shutdownHandler(){ - if($this->cleanShutdown !== true){ + if($this->cleanShutdown !== true && $this->crashInfo === null){ $error = error_get_last(); if($error !== null){ $this->logger->emergency("Fatal error: " . $error["message"] . " in " . $error["file"] . " on line " . $error["line"]); - $this->setCrashInfo($error['message']); + $this->setCrashInfo(RakLibThreadCrashInfo::fromLastErrorInfo($error)); }else{ $this->logger->emergency("RakLib shutdown unexpectedly"); } } } - public function getCrashInfo() : ?string{ + public function getCrashInfo() : ?RakLibThreadCrashInfo{ return $this->crashInfo; } - private function setCrashInfo(string $info) : void{ - $this->synchronized(function(string $info) : void{ + private function setCrashInfo(RakLibThreadCrashInfo $info) : void{ + $this->synchronized(function(RakLibThreadCrashInfo $info) : void{ $this->crashInfo = $info; $this->notify(); }, $info); @@ -131,8 +132,12 @@ class RakLibServer extends Thread{ while(!$this->ready and $this->crashInfo === null){ $this->wait(); } - if($this->crashInfo !== null){ - throw new \RuntimeException("RakLib failed to start: $this->crashInfo"); + $crashInfo = $this->crashInfo; + if($crashInfo !== null){ + if($crashInfo->getClass() === SocketException::class){ + throw new SocketException($crashInfo->getMessage()); + } + throw new \RuntimeException("RakLib failed to start: " . $crashInfo->makePrettyMessage()); } }); } @@ -145,7 +150,12 @@ class RakLibServer extends Thread{ register_shutdown_function([$this, "shutdownHandler"]); - $socket = new Socket($this->address); + try{ + $socket = new Socket($this->address); + }catch(SocketException $e){ + $this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e)); + return; + } $manager = new Server( $this->serverId, $this->logger, @@ -166,7 +176,7 @@ class RakLibServer extends Thread{ $manager->waitShutdown(); $this->cleanShutdown = true; }catch(\Throwable $e){ - $this->setCrashInfo($e->getMessage()); + $this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e)); $this->logger->logException($e); } } diff --git a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php b/src/network/mcpe/raklib/RakLibThreadCrashInfo.php new file mode 100644 index 000000000..826cdc42d --- /dev/null +++ b/src/network/mcpe/raklib/RakLibThreadCrashInfo.php @@ -0,0 +1,61 @@ +getMessage(), $e->getFile(), $e->getLine()); + } + + /** + * @phpstan-param array{message: string, file: string, line: int} $info + */ + public static function fromLastErrorInfo(array $info) : self{ + return new self(null, $info["message"], $info["file"], $info["line"]); + } + + /** @return string|null */ + public function getClass() : ?string{ return $this->class; } + + public function getMessage() : string{ return $this->message; } + + public function getFile() : string{ return $this->file; } + + public function getLine() : int{ return $this->line; } + + public function makePrettyMessage() : string{ + return sprintf("%s: \"%s\" in %s on line %d", $this->class ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line); + } +} \ No newline at end of file