Use Snooze to improve AsyncTask collection times

regardless of how long an async task takes to run, it will take a multiple of 50ms to get the result processed. This delay causes issues in some cases for stuff like generation, which causes locking of adjacent chunks, and async packet compression, which experiences elevated latency because of this problem.
This is not an ideal solution for packet compression since it will cause the sleeper handler to get hammered, but since it's already getting hammered by every packet from RakLib, I don't think that's a big problem.
This commit is contained in:
Dylan K. Taylor 2020-12-02 19:34:34 +00:00
parent 1775fb669b
commit 29f6ed3f68
6 changed files with 73 additions and 36 deletions

View File

@ -873,7 +873,7 @@ class Server{
$poolSize = max(1, (int) $poolSize); $poolSize = max(1, (int) $poolSize);
} }
$this->asyncPool = new AsyncPool($poolSize, max(-1, (int) $this->configGroup->getProperty("memory.async-worker-hard-limit", 256)), $this->autoloader, $this->logger); $this->asyncPool = new AsyncPool($poolSize, max(-1, (int) $this->configGroup->getProperty("memory.async-worker-hard-limit", 256)), $this->autoloader, $this->logger, $this->tickSleeper);
$netCompressionThreshold = -1; $netCompressionThreshold = -1;
if($this->configGroup->getProperty("network.batch-threshold", 256) >= 0){ if($this->configGroup->getProperty("network.batch-threshold", 256) >= 0){

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\scheduler; namespace pocketmine\scheduler;
use pocketmine\snooze\SleeperHandler;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use function array_keys; use function array_keys;
use function array_map; use function array_map;
@ -73,11 +75,15 @@ class AsyncPool{
*/ */
private $workerStartHooks = []; private $workerStartHooks = [];
public function __construct(int $size, int $workerMemoryLimit, \ClassLoader $classLoader, \ThreadedLogger $logger){ /** @var SleeperHandler */
private $eventLoop;
public function __construct(int $size, int $workerMemoryLimit, \ClassLoader $classLoader, \ThreadedLogger $logger, SleeperHandler $eventLoop){
$this->size = $size; $this->size = $size;
$this->workerMemoryLimit = $workerMemoryLimit; $this->workerMemoryLimit = $workerMemoryLimit;
$this->classLoader = $classLoader; $this->classLoader = $classLoader;
$this->logger = $logger; $this->logger = $logger;
$this->eventLoop = $eventLoop;
} }
/** /**
@ -136,8 +142,11 @@ class AsyncPool{
*/ */
private function getWorker(int $worker) : AsyncWorker{ private function getWorker(int $worker) : AsyncWorker{
if(!isset($this->workers[$worker])){ if(!isset($this->workers[$worker])){
$notifier = new SleeperNotifier();
$this->workers[$worker] = new AsyncWorker($this->logger, $worker, $this->workerMemoryLimit); $this->workers[$worker] = new AsyncWorker($this->logger, $worker, $this->workerMemoryLimit, $notifier);
$this->eventLoop->addNotifier($notifier, function() use ($worker) : void{
$this->collectTasksFromWorker($worker);
});
$this->workers[$worker]->setClassLoader($this->classLoader); $this->workers[$worker]->setClassLoader($this->classLoader);
$this->workers[$worker]->start(self::WORKER_START_OPTIONS); $this->workers[$worker]->start(self::WORKER_START_OPTIONS);
@ -226,7 +235,18 @@ class AsyncPool{
public function collectTasks() : bool{ public function collectTasks() : bool{
$more = false; $more = false;
foreach($this->taskQueues as $worker => $queue){ foreach($this->taskQueues as $worker => $queue){
$more = $this->collectTasksFromWorker($worker) || $more;
}
return $more;
}
public function collectTasksFromWorker(int $worker) : bool{
if(!isset($this->taskQueues[$worker])){
throw new \InvalidArgumentException("No such worker $worker");
}
$queue = $this->taskQueues[$worker];
$doGC = false; $doGC = false;
$more = false;
while(!$queue->isEmpty()){ while(!$queue->isEmpty()){
/** @var AsyncTask $task */ /** @var AsyncTask $task */
$task = $queue->bottom(); $task = $queue->bottom();
@ -259,7 +279,6 @@ class AsyncPool{
if($doGC){ if($doGC){
$this->workers[$worker]->collect(); $this->workers[$worker]->collect();
} }
}
return $more; return $more;
} }
@ -279,6 +298,7 @@ class AsyncPool{
foreach($this->taskQueues as $i => $queue){ foreach($this->taskQueues as $i => $queue){
if((!isset($this->workerLastUsed[$i]) or $this->workerLastUsed[$i] + 300 < $time) and $queue->isEmpty()){ if((!isset($this->workerLastUsed[$i]) or $this->workerLastUsed[$i] + 300 < $time) and $queue->isEmpty()){
$this->workers[$i]->quit(); $this->workers[$i]->quit();
$this->eventLoop->removeNotifier($this->workers[$i]->getNotifier());
unset($this->workers[$i], $this->taskQueues[$i], $this->workerLastUsed[$i]); unset($this->workers[$i], $this->taskQueues[$i], $this->workerLastUsed[$i]);
$ret++; $ret++;
} }
@ -309,6 +329,7 @@ class AsyncPool{
foreach($this->workers as $worker){ foreach($this->workers as $worker){
$worker->quit(); $worker->quit();
$this->eventLoop->removeNotifier($worker->getNotifier());
} }
$this->workers = []; $this->workers = [];
$this->taskQueues = []; $this->taskQueues = [];

View File

@ -89,6 +89,7 @@ abstract class AsyncTask extends \Threaded{
} }
$this->finished = true; $this->finished = true;
$this->worker->getNotifier()->wakeupSleeper();
} }
public function isCrashed() : bool{ public function isCrashed() : bool{

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\scheduler; namespace pocketmine\scheduler;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\thread\Worker; use pocketmine\thread\Worker;
use function gc_enable; use function gc_enable;
use function ini_set; use function ini_set;
@ -39,10 +40,18 @@ class AsyncWorker extends Worker{
/** @var int */ /** @var int */
private $memoryLimit; private $memoryLimit;
public function __construct(\ThreadedLogger $logger, int $id, int $memoryLimit){ /** @var SleeperNotifier */
private $notifier;
public function __construct(\ThreadedLogger $logger, int $id, int $memoryLimit, SleeperNotifier $notifier){
$this->logger = $logger; $this->logger = $logger;
$this->id = $id; $this->id = $id;
$this->memoryLimit = $memoryLimit; $this->memoryLimit = $memoryLimit;
$this->notifier = $notifier;
}
public function getNotifier() : SleeperNotifier{
return $this->notifier;
} }
protected function onRun() : void{ protected function onRun() : void{

View File

@ -460,6 +460,11 @@ parameters:
count: 1 count: 1
path: ../../../src/scheduler/AsyncTask.php path: ../../../src/scheduler/AsyncTask.php
-
message: "#^Cannot call method getNotifier\\(\\) on pocketmine\\\\scheduler\\\\AsyncWorker\\|null\\.$#"
count: 1
path: ../../../src/scheduler/AsyncTask.php
- -
message: "#^Cannot call method count\\(\\) on ArrayObject\\<int, array\\<string, mixed\\>\\>\\|null\\.$#" message: "#^Cannot call method count\\(\\) on ArrayObject\\<int, array\\<string, mixed\\>\\>\\|null\\.$#"
count: 1 count: 1

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\scheduler; namespace pocketmine\scheduler;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use pocketmine\snooze\SleeperHandler;
use pocketmine\utils\MainLogger; use pocketmine\utils\MainLogger;
use pocketmine\utils\Terminal; use pocketmine\utils\Terminal;
use function define; use function define;
@ -44,7 +45,7 @@ class AsyncPoolTest extends TestCase{
Terminal::init(); Terminal::init();
@define('pocketmine\\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__, 3) . '/vendor/autoload.php'); @define('pocketmine\\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__, 3) . '/vendor/autoload.php');
$this->mainLogger = new MainLogger(tempnam(sys_get_temp_dir(), "pmlog")); $this->mainLogger = new MainLogger(tempnam(sys_get_temp_dir(), "pmlog"));
$this->pool = new AsyncPool(2, 1024, new \BaseClassLoader(), $this->mainLogger); $this->pool = new AsyncPool(2, 1024, new \BaseClassLoader(), $this->mainLogger, new SleeperHandler());
} }
public function tearDown() : void{ public function tearDown() : void{