From 2a0a2134d135808075e1ed5087926e5f2190cd19 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 9 May 2018 14:18:13 +0100 Subject: [PATCH] Server: Implemented an signal/sleep interrupt mechanism for ticking (#2171) This allows other threads to notify the main thread to wake it up while it's sleeping between ticks, allowing reduction of processing latency. Currently only RakLib and the CommandReader threads utilize this, but it's planned to extend it to more things in the near future. CommandReader is now event-driven instead of poll-based - the server will not poll the CommandReader thread for messages each tick anymore. RakLib utilizes this mechanism to get packets processed without delays to lower latency. This now adds an extra dependency - `pocketmine/snooze` library contains the meat of the code used for this. See the Snooze repository for details. --- composer.json | 9 +++- composer.lock | 48 ++++++++++++++++--- src/pocketmine/Server.php | 32 ++++++++----- src/pocketmine/command/CommandReader.php | 11 ++++- src/pocketmine/network/Network.php | 28 ++++++----- .../network/mcpe/RakLibInterface.php | 14 +++++- 6 files changed, 108 insertions(+), 34 deletions(-) diff --git a/composer.json b/composer.json index d2a506e97..7e29deb47 100644 --- a/composer.json +++ b/composer.json @@ -22,11 +22,12 @@ "ext-yaml": ">=2.0.0", "ext-zip": "*", "ext-zlib": ">=1.2.11", - "pocketmine/raklib": "dev-master#5de9f0dc4b076c454efdb075450737fe312d60ce", + "pocketmine/raklib": "dev-master#8a892d1b48142d091179ccd8abe64d11c6eede99", "pocketmine/spl": "0.3.0", "pocketmine/binaryutils": "dev-master#c824ac67eeeb6899c2a9ec91a769eb9ed6e3f595", "pocketmine/nbt": "dev-master#09809564c7e58c322dcefc6905ab333313e05d1f", - "pocketmine/math": "dev-master#bd78ce8ad1c0a583de6655eee46a82ccaade1302" + "pocketmine/math": "dev-master#bd78ce8ad1c0a583de6655eee46a82ccaade1302", + "pocketmine/snooze": "dev-master#96c740826df04024d2b685aa5ba8b8d0bdc69439" }, "autoload": { "psr-4": { @@ -53,6 +54,10 @@ { "type": "vcs", "url": "https://github.com/pmmp/Math" + }, + { + "type": "vcs", + "url": "https://github.com/pmmp/Snooze" } ] } diff --git a/composer.lock b/composer.lock index ee69ba454..72a7e48a4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "0a9989451e8593a15cde64bf52466e4b", + "content-hash": "0f24757ea6a3627535e61e9bded163be", "packages": [ { "name": "pocketmine/binaryutils", @@ -120,12 +120,12 @@ "source": { "type": "git", "url": "https://github.com/pmmp/RakLib.git", - "reference": "5de9f0dc4b076c454efdb075450737fe312d60ce" + "reference": "8a892d1b48142d091179ccd8abe64d11c6eede99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/RakLib/zipball/5de9f0dc4b076c454efdb075450737fe312d60ce", - "reference": "5de9f0dc4b076c454efdb075450737fe312d60ce", + "url": "https://api.github.com/repos/pmmp/RakLib/zipball/8a892d1b48142d091179ccd8abe64d11c6eede99", + "reference": "8a892d1b48142d091179ccd8abe64d11c6eede99", "shasum": "" }, "require": { @@ -136,6 +136,7 @@ "php-64bit": "*", "php-ipv6": "*", "pocketmine/binaryutils": "dev-master#c824ac67eeeb6899c2a9ec91a769eb9ed6e3f595", + "pocketmine/snooze": "dev-master#96c740826df04024d2b685aa5ba8b8d0bdc69439", "pocketmine/spl": "0.3.0" }, "type": "library", @@ -152,7 +153,41 @@ "source": "https://github.com/pmmp/RakLib/tree/master", "issues": "https://github.com/pmmp/RakLib/issues" }, - "time": "2018-04-16T09:12:34+00:00" + "time": "2018-05-09T12:41:15+00:00" + }, + { + "name": "pocketmine/snooze", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/pmmp/Snooze.git", + "reference": "96c740826df04024d2b685aa5ba8b8d0bdc69439" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pmmp/Snooze/zipball/96c740826df04024d2b685aa5ba8b8d0bdc69439", + "reference": "96c740826df04024d2b685aa5ba8b8d0bdc69439", + "shasum": "" + }, + "require": { + "ext-pthreads": ">=3.1.7dev", + "php-64bit": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "pocketmine\\snooze\\": "src/" + } + }, + "license": [ + "LGPL-3.0" + ], + "description": "Thread notification management library for code using the pthreads extension", + "support": { + "source": "https://github.com/pmmp/Snooze/tree/master", + "issues": "https://github.com/pmmp/Snooze/issues" + }, + "time": "2018-05-09T12:39:21+00:00" }, { "name": "pocketmine/spl", @@ -195,7 +230,8 @@ "pocketmine/raklib": 20, "pocketmine/binaryutils": 20, "pocketmine/nbt": 20, - "pocketmine/math": 20 + "pocketmine/math": 20, + "pocketmine/snooze": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 908f978d6..c16400a3c 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -90,6 +90,8 @@ use pocketmine\resourcepacks\ResourcePackManager; use pocketmine\scheduler\FileWriteTask; use pocketmine\scheduler\SendUsageTask; use pocketmine\scheduler\ServerScheduler; +use pocketmine\snooze\SleeperHandler; +use pocketmine\snooze\SleeperNotifier; use pocketmine\tile\Tile; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; @@ -116,6 +118,9 @@ class Server{ /** @var \Threaded */ private static $sleeper = null; + /** @var SleeperHandler */ + private $tickSleeper; + /** @var BanList */ private $banByName = null; @@ -1404,6 +1409,7 @@ class Server{ } self::$instance = $this; self::$sleeper = new \Threaded; + $this->tickSleeper = new SleeperHandler(); $this->autoloader = $autoloader; $this->logger = $logger; @@ -1423,7 +1429,11 @@ class Server{ $this->dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR; $this->pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR; - $this->console = new CommandReader(); + $consoleNotifier = new SleeperNotifier(); + $this->console = new CommandReader($consoleNotifier); + $this->tickSleeper->addNotifier($consoleNotifier, function() : void{ + $this->checkConsole(); + }); $version = new VersionString($this->getPocketMineVersion()); @@ -1925,7 +1935,7 @@ class Server{ public function checkConsole(){ Timings::$serverCommandTimer->startTiming(); - if(($line = $this->console->getLine()) !== null){ + while(($line = $this->console->getLine()) !== null){ $this->pluginManager->callEvent($ev = new ServerCommandEvent($this->consoleSender, $line)); if(!$ev->isCancelled()){ $this->dispatchCommand($ev->getSender(), $ev->getCommand()); @@ -2237,18 +2247,18 @@ class Server{ return []; } + public function getTickSleeper() : SleeperHandler{ + return $this->tickSleeper; + } + private function tickProcessor(){ $this->nextTick = microtime(true); + while($this->isRunning){ $this->tick(); - $next = $this->nextTick - 0.0001; - if($next > microtime(true)){ - try{ - @time_sleep_until($next); - }catch(\Throwable $e){ - //Sometimes $next is less than the current time. High load? - } - } + + //sleeps are self-correcting - if we undersleep 1ms on this tick, we'll sleep an extra ms on the next tick + $this->tickSleeper->sleepUntil($this->nextTick); } } @@ -2489,8 +2499,6 @@ class Server{ ++$this->tickCounter; - $this->checkConsole(); - Timings::$connectionTimer->startTiming(); $this->network->processInterfaces(); diff --git a/src/pocketmine/command/CommandReader.php b/src/pocketmine/command/CommandReader.php index 2d60e5a7b..4c788a88c 100644 --- a/src/pocketmine/command/CommandReader.php +++ b/src/pocketmine/command/CommandReader.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\command; +use pocketmine\snooze\SleeperNotifier; use pocketmine\Thread; class CommandReader extends Thread{ @@ -36,8 +37,13 @@ class CommandReader extends Thread{ private $shutdown = false; private $type = self::TYPE_STREAM; - public function __construct(){ + /** @var SleeperNotifier|null */ + private $notifier; + + public function __construct(?SleeperNotifier $notifier = null){ $this->buffer = new \Threaded; + $this->notifier = $notifier; + $opts = getopt("", ["disable-readline"]); if(extension_loaded("readline") and !isset($opts["disable-readline"]) and !$this->isPipe(STDIN)){ @@ -142,6 +148,9 @@ class CommandReader extends Thread{ if($line !== ""){ $this->buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $line); + if($this->notifier !== null){ + $this->notifier->wakeupSleeper(); + } } return true; diff --git a/src/pocketmine/network/Network.php b/src/pocketmine/network/Network.php index edc1ccda2..6edb9977f 100644 --- a/src/pocketmine/network/Network.php +++ b/src/pocketmine/network/Network.php @@ -85,20 +85,24 @@ class Network{ public function processInterfaces(){ foreach($this->interfaces as $interface){ - try{ - $interface->process(); - }catch(\Throwable $e){ - $logger = $this->server->getLogger(); - if(\pocketmine\DEBUG > 1){ - $logger->logException($e); - } + $this->processInterface($interface); + } + } - $this->server->getPluginManager()->callEvent(new NetworkInterfaceCrashEvent($interface, $e)); - - $interface->emergencyShutdown(); - $this->unregisterInterface($interface); - $logger->critical($this->server->getLanguage()->translateString("pocketmine.server.networkError", [get_class($interface), $e->getMessage()])); + public function processInterface(SourceInterface $interface) : void{ + try{ + $interface->process(); + }catch(\Throwable $e){ + $logger = $this->server->getLogger(); + if(\pocketmine\DEBUG > 1){ + $logger->logException($e); } + + $this->server->getPluginManager()->callEvent(new NetworkInterfaceCrashEvent($interface, $e)); + + $interface->emergencyShutdown(); + $this->unregisterInterface($interface); + $logger->critical($this->server->getLanguage()->translateString("pocketmine.server.networkError", [get_class($interface), $e->getMessage()])); } } diff --git a/src/pocketmine/network/mcpe/RakLibInterface.php b/src/pocketmine/network/mcpe/RakLibInterface.php index 1002353de..f41ee638d 100644 --- a/src/pocketmine/network/mcpe/RakLibInterface.php +++ b/src/pocketmine/network/mcpe/RakLibInterface.php @@ -32,6 +32,7 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\Network; use pocketmine\Player; use pocketmine\Server; +use pocketmine\snooze\SleeperNotifier; use raklib\protocol\EncapsulatedPacket; use raklib\protocol\PacketReliability; use raklib\RakLib; @@ -68,15 +69,24 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{ /** @var ServerHandler */ private $interface; + /** @var SleeperNotifier */ + private $sleeper; + public function __construct(Server $server){ $this->server = $server; + $this->sleeper = new SleeperNotifier(); + $server->getTickSleeper()->addNotifier($this->sleeper, function() : void{ + $this->server->getNetwork()->processInterface($this); + }); + $this->rakLib = new RakLibServer( $this->server->getLogger(), \pocketmine\COMPOSER_AUTOLOADER_PATH, new InternetAddress($this->server->getIp() === "" ? "0.0.0.0" : $this->server->getIp(), $this->server->getPort(), 4), (int) $this->server->getProperty("network.max-mtu-size", 1492), - self::MCPE_RAKNET_PROTOCOL_VERSION + self::MCPE_RAKNET_PROTOCOL_VERSION, + $this->sleeper ); $this->interface = new ServerHandler($this->rakLib, $this); } @@ -117,10 +127,12 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{ } public function shutdown(){ + $this->server->getTickSleeper()->removeNotifier($this->sleeper); $this->interface->shutdown(); } public function emergencyShutdown(){ + $this->server->getTickSleeper()->removeNotifier($this->sleeper); $this->interface->emergencyShutdown(); }