Merge branch 'master' into mcpe-1.2

This commit is contained in:
Dylan K. Taylor 2017-09-14 18:16:45 +01:00
commit 6b34c47c96
12 changed files with 220 additions and 52 deletions

View File

@ -25,7 +25,9 @@ namespace pocketmine;
use pocketmine\event\server\LowMemoryEvent;
use pocketmine\event\Timings;
use pocketmine\scheduler\DumpWorkerMemoryTask;
use pocketmine\scheduler\GarbageCollectionTask;
use pocketmine\utils\MainLogger;
use pocketmine\utils\Utils;
class MemoryManager{
@ -74,6 +76,9 @@ class MemoryManager{
/** @var bool */
private $cacheTrigger;
/** @var bool */
private $dumpWorkers = true;
public function __construct(Server $server){
$this->server = $server;
@ -131,6 +136,7 @@ class MemoryManager{
$this->chunkCache = (bool) $this->server->getProperty("memory.world-caches.disable-chunk-cache", true);
$this->cacheTrigger = (bool) $this->server->getProperty("memory.world-caches.low-memory-trigger", true);
$this->dumpWorkers = (bool) $this->server->getProperty("memory.memory-dump.dump-async-worker", true);
gc_enable();
}
@ -261,6 +267,27 @@ class MemoryManager{
* @param int $maxStringSize
*/
public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize){
MainLogger::getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
self::dumpMemory($this->server, $this->server->getLoader(), $outputFolder, $maxNesting, $maxStringSize);
if($this->dumpWorkers){
$scheduler = $this->server->getScheduler();
for($i = 0, $size = $scheduler->getAsyncTaskPoolSize(); $i < $size; ++$i){
$scheduler->scheduleAsyncTaskToWorker(new DumpWorkerMemoryTask($outputFolder, $maxNesting, $maxStringSize), $i);
}
}
}
/**
* Static memory dumper accessible from any thread.
*
* @param mixed $startingObject
* @param \ClassLoader $loader
* @param string $outputFolder
* @param int $maxNesting
* @param int $maxStringSize
*/
public static function dumpMemory($startingObject, \ClassLoader $loader, string $outputFolder, int $maxNesting, int $maxStringSize){
$hardLimit = ini_get('memory_limit');
ini_set('memory_limit', '-1');
gc_disable();
@ -269,12 +296,8 @@ class MemoryManager{
mkdir($outputFolder, 0777, true);
}
$this->server->getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
$obData = fopen($outputFolder . "/objects.js", "wb+");
$staticProperties = [];
$data = [];
$objects = [];
@ -283,8 +306,10 @@ class MemoryManager{
$instanceCounts = [];
$staticProperties = [];
$staticCount = 0;
foreach($this->server->getLoader()->getClasses() as $className){
foreach($loader->getClasses() as $className){
$reflection = new \ReflectionClass($className);
$staticProperties[$className] = [];
foreach($reflection->getProperties() as $property){
@ -297,7 +322,7 @@ class MemoryManager{
}
$staticCount++;
$this->continueDump($property->getValue(), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
self::continueDump($property->getValue(), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($staticProperties[$className]) === 0){
@ -305,9 +330,39 @@ class MemoryManager{
}
}
echo "[Dump] Wrote $staticCount static properties\n";
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
MainLogger::getLogger()->info("[Dump] Wrote $staticCount static properties");
$this->continueDump($this->server, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
if($GLOBALS !== null){ //This might be null if we're on a different thread
$globalVariables = [];
$globalCount = 0;
$ignoredGlobals = [
'GLOBALS' => true,
'_SERVER' => true,
'_REQUEST' => true,
'_POST' => true,
'_GET' => true,
'_FILES' => true,
'_ENV' => true,
'_COOKIE' => true,
'_SESSION' => true
];
foreach($GLOBALS as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
$globalCount++;
self::continueDump($value, $globalVariables[$varName], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
file_put_contents($outputFolder . "/globalVariables.js", json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
MainLogger::getLogger()->info("[Dump] Wrote $globalCount global variables");
}
self::continueDump($startingObject, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
do{
$continue = false;
@ -349,25 +404,26 @@ class MemoryManager{
if(!$property->isPublic()){
$property->setAccessible(true);
}
$this->continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
self::continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
fwrite($obData, "$hash@$className: " . json_encode($info, JSON_UNESCAPED_SLASHES) . "\n");
}
echo "[Dump] Wrote " . count($objects) . " objects\n";
}while($continue);
MainLogger::getLogger()->info("[Dump] Wrote " . count($objects) . " objects");
fclose($obData);
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
file_put_contents($outputFolder . "/serverEntry.js", json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
file_put_contents($outputFolder . "/referenceCounts.js", json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
arsort($instanceCounts, SORT_NUMERIC);
file_put_contents($outputFolder . "/instanceCounts.js", json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
echo "[Dump] Finished!\n";
MainLogger::getLogger()->info("[Dump] Finished!");
ini_set('memory_limit', $hardLimit);
gc_enable();
@ -382,7 +438,7 @@ class MemoryManager{
* @param int $maxNesting
* @param int $maxStringSize
*/
private function continueDump($from, &$data, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
private static function continueDump($from, &$data, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
if($maxNesting <= 0){
$data = "(error) NESTING LIMIT REACHED";
return;
@ -406,7 +462,7 @@ class MemoryManager{
}
$data = [];
foreach($from as $key => $value){
$this->continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
self::continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
}
}elseif(is_string($from)){
$data = "(string) len(". strlen($from) .") " . substr(Utils::printable($from), 0, $maxStringSize);

View File

@ -80,7 +80,7 @@ namespace pocketmine {
use raklib\RakLib;
const VERSION = "1.7dev";
const API_VERSION = "3.0.0-ALPHA7";
const API_VERSION = "3.0.0-ALPHA8";
const CODENAME = "[REDACTED]";
/*
@ -558,19 +558,7 @@ namespace pocketmine {
$killer->start();
usleep(10000); //Fixes ServerKiller not being able to start on single-core machines
$erroredThreads = 0;
foreach(ThreadManager::getInstance()->getAll() as $id => $thread){
$logger->debug("Stopping " . $thread->getThreadName() . " thread");
try{
$thread->quit();
$logger->debug($thread->getThreadName() . " thread stopped successfully.");
}catch(\ThreadException $e){
++$erroredThreads;
$logger->debug("Could not stop " . $thread->getThreadName() . " thread: " . $e->getMessage());
}
}
if($erroredThreads > 0){
if(ThreadManager::getInstance()->stopAll() > 0){
if(\pocketmine\DEBUG > 1){
echo "Some threads could not be stopped, performing a force-kill" . PHP_EOL . PHP_EOL;
}

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine;
use pocketmine\utils\MainLogger;
class ThreadManager extends \Volatile{
/** @var ThreadManager */
@ -68,4 +70,23 @@ class ThreadManager extends \Volatile{
return $array;
}
public function stopAll() : int{
$logger = MainLogger::getLogger();
$erroredThreads = 0;
foreach($this->getAll() as $thread){
$logger->debug("Stopping " . $thread->getThreadName() . " thread");
try{
$thread->quit();
$logger->debug($thread->getThreadName() . " thread stopped successfully.");
}catch(\ThreadException $e){
++$erroredThreads;
$logger->debug("Could not stop " . $thread->getThreadName() . " thread: " . $e->getMessage());
}
}
return $erroredThreads;
}
}

View File

@ -341,7 +341,7 @@ class BlockFactory{
public static function registerBlock(Block $block, bool $override = false){
$id = $block->getId();
if(self::$list[$id] !== null and !(self::$list[$id] instanceof UnknownBlock) and !$override){
if(!$override and self::isRegistered($id)){
throw new \RuntimeException("Trying to overwrite an already registered block");
}
@ -403,4 +403,15 @@ class BlockFactory{
public static function getBlockStatesArray() : \SplFixedArray{
return self::$fullList;
}
/**
* Returns whether a specified block ID is already registered in the block factory.
*
* @param int $id
* @return bool
*/
public static function isRegistered(int $id) : bool{
$b = self::$list[$id];
return $b !== null and !($b instanceof UnknownBlock);
}
}

View File

@ -256,7 +256,7 @@ class ItemFactory{
*/
public static function registerItem(Item $item, bool $override = false){
$id = $item->getId();
if(!$override and self::$list[$id] !== null){
if(!$override and self::isRegistered($id)){
throw new \RuntimeException("Trying to overwrite an already registered item");
}
@ -350,4 +350,17 @@ class ItemFactory{
return $item;
}
}
/**
* Returns whether the specified item ID is already registered in the item factory.
*
* @param int $id
* @return bool
*/
public static function isRegistered(int $id) : bool{
if($id < 256){
return BlockFactory::isRegistered($id);
}
return self::$list[$id] !== null;
}
}

View File

@ -41,12 +41,9 @@ class Location extends Position{
* @param Level $level
*/
public function __construct($x = 0, $y = 0, $z = 0, $yaw = 0.0, $pitch = 0.0, Level $level = null){
$this->x = $x;
$this->y = $y;
$this->z = $z;
$this->yaw = $yaw;
$this->pitch = $pitch;
$this->level = $level;
parent::__construct($x, $y, $z, $level);
}
/**

View File

@ -62,6 +62,12 @@ memory:
#Trigger on low memory
low-memory-trigger: true
#Settings controlling memory dump handling.
memory-dump:
#Dump memory from async workers as well as the main thread. If you have issues with segfaults when dumping memory, disable this setting.
dump-async-worker: true
max-chunks:
#Maximum render distance per player when low memory is triggered
chunk-radius: 4

View File

@ -64,4 +64,8 @@ class AsyncWorker extends Worker{
public function getThreadName() : string{
return "Asynchronous Worker #" . $this->id;
}
public function getAsyncWorkerId() : int{
return $this->id;
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\scheduler;
use pocketmine\MemoryManager;
/**
* Task used to dump memory from AsyncWorkers
*/
class DumpWorkerMemoryTask extends AsyncTask{
/** @var string */
private $outputFolder;
/** @var int */
private $maxNesting;
/** @var int */
private $maxStringSize;
/**
* @param string $outputFolder
* @param int $maxNesting
* @param int $maxStringSize
*/
public function __construct(string $outputFolder, int $maxNesting, int $maxStringSize){
$this->outputFolder = $outputFolder;
$this->maxNesting = $maxNesting;
$this->maxStringSize = $maxStringSize;
}
public function onRun(){
global $store;
MemoryManager::dumpMemory(
["worker" => $this->worker, "store" => $store],
$this->worker->getClassLoader(),
$this->outputFolder . DIRECTORY_SEPARATOR . "AsyncWorker#" . $this->worker->getAsyncWorkerId(),
$this->maxNesting,
$this->maxStringSize
);
}
}

View File

@ -89,15 +89,17 @@ class AutoUpdater{
* Posts a warning to the console to tell the user there is an update available
*/
public function showConsoleUpdate(){
$logger = $this->server->getLogger();
$newVersion = new VersionString($this->updateInfo["version"]);
$logger->warning("----- PocketMine-MP Auto Updater -----");
$logger->warning("Your version of PocketMine-MP is out of date. Version " . $newVersion->get(false) . " (build #" . $newVersion->getBuild() . ") was released on " . date("D M j h:i:s Y", $this->updateInfo["date"]));
$messages = [
"Your version of " . $this->server->getName() . " is out of date. Version " . $newVersion->get(false) . " (build #" . $newVersion->getBuild() . ") was released on " . date("D M j h:i:s Y", $this->updateInfo["date"])
];
if($this->updateInfo["details_url"] !== null){
$logger->warning("Details: " . $this->updateInfo["details_url"]);
$messages[] = "Details: " . $this->updateInfo["details_url"];
}
$logger->warning("Download: " . $this->updateInfo["download_url"]);
$logger->warning("----- -------------------------- -----");
$messages[] = "Download: " . $this->updateInfo["download_url"];
$this->printConsoleMessage($messages, \LogLevel::WARNING);
}
/**
@ -105,24 +107,33 @@ class AutoUpdater{
* @param Player $player
*/
public function showPlayerUpdate(Player $player){
$player->sendMessage(TextFormat::DARK_PURPLE . "The version of PocketMine-MP that this server is running is out of date. Please consider updating to the latest version.");
$player->sendMessage(TextFormat::DARK_PURPLE . "The version of " . $this->server->getName() . " that this server is running is out of date. Please consider updating to the latest version.");
$player->sendMessage(TextFormat::DARK_PURPLE . "Check the console for more details.");
}
protected function showChannelSuggestionStable(){
$logger = $this->server->getLogger();
$logger->info("----- PocketMine-MP Auto Updater -----");
$logger->info("It appears you're running a Stable build, when you've specified that you prefer to run " . ucfirst($this->getChannel()) . " builds.");
$logger->info("If you would like to be kept informed about new Stable builds only, it is recommended that you change 'preferred-channel' in your pocketmine.yml to 'stable'.");
$logger->info("----- -------------------------- -----");
$this->printConsoleMessage([
"It appears you're running a Stable build, when you've specified that you prefer to run " . ucfirst($this->getChannel()) . " builds.",
"If you would like to be kept informed about new Stable builds only, it is recommended that you change 'preferred-channel' in your pocketmine.yml to 'stable'."
]);
}
protected function showChannelSuggestionBeta(){
$this->printConsoleMessage([
"It appears you're running a Beta build, when you've specified that you prefer to run Stable builds.",
"If you would like to be kept informed about new Beta or Development builds, it is recommended that you change 'preferred-channel' in your pocketmine.yml to 'beta' or 'development'."
]);
}
protected function printConsoleMessage(array $lines, string $logLevel = \LogLevel::INFO){
$logger = $this->server->getLogger();
$logger->info("----- PocketMine-MP Auto Updater -----");
$logger->info("It appears you're running a Beta build, when you've specified that you prefer to run Stable builds.");
$logger->info("If you would like to be kept informed about new Beta or Development builds, it is recommended that you change 'preferred-channel' in your pocketmine.yml to 'beta' or 'development'.");
$logger->info("----- -------------------------- -----");
$title = $this->server->getName() . ' Auto Updater';
$logger->log($logLevel, sprintf('----- %s -----', $title));
foreach($lines as $line){
$logger->log($logLevel, $line);
}
$logger->log($logLevel, sprintf('----- %s -----', str_repeat('-', strlen($title))));
}
/**

@ -1 +1 @@
Subproject commit bfae42ce8985204b0db276d0b8daed7050567323
Subproject commit eec5da2443b84821bd7e7224adea4921c81dc674

@ -1 +1 @@
Subproject commit c568b5ec9bd0606f0334d28ba60b0fc6c624a8f9
Subproject commit 87d6b81989af33bfffea030e0015c3b6d43dc8e0