Added MemoryManager object watcher for object leak debugging, improved SPL Thread/Worker stopping, fixed some possible weak references crashing unexpectedly

This commit is contained in:
Shoghi Cervantes 2015-05-09 16:10:03 +02:00
parent 7e539ec85a
commit 1a1b8830a4
No known key found for this signature in database
GPG Key ID: 78464DB0A7837F89
10 changed files with 158 additions and 30 deletions

View File

@ -54,6 +54,13 @@ class MemoryManager{
private $chunkCache;
private $cacheTrigger;
/** @var \WeakRef[] */
private $leakWatch = [];
private $leakInfo = [];
private $leakSeed = 0;
public function __construct(Server $server){
$this->server = $server;
@ -172,4 +179,85 @@ class MemoryManager{
return $cycles;
}
/**
* @param object $object
*
* @return string Object identifier for future checks
*/
public function addObjectWatcher($object){
if(!is_object($object)){
throw new \InvalidArgumentException("Not an object!");
}
$identifier = spl_object_hash($object) . ":" . get_class($object);
if(isset($this->leakInfo[$identifier])){
return $this->leakInfo["id"];
}
$this->leakInfo[$identifier] = [
"id" => $id = Utils::dataToUUID($identifier . ":" . $this->leakSeed++),
"class" => get_class($object),
"hash" => $identifier
];
$this->leakInfo[$id] = $this->leakInfo[$identifier];
$this->leakWatch[$id] = new \WeakRef($object);
return $id;
}
public function isObjectAlive($id){
if(isset($this->leakWatch[$id])){
return $this->leakWatch[$id]->valid();
}
return false;
}
public function removeObjectWatch($id){
if(!isset($this->leakWatch[$id])){
return;
}
unset($this->leakInfo[$this->leakInfo[$id]["hash"]]);
unset($this->leakInfo[$id]);
unset($this->leakWatch[$id]);
}
public function doObjectCleanup(){
foreach($this->leakWatch as $id => $w){
if(!$w->valid()){
$this->removeObjectWatch($id);
}
}
}
public function getObjectInformation($id, $includeObject = false){
if(!isset($this->leakWatch[$id])){
return null;
}
$valid = false;
$references = 0;
$object = null;
if($this->leakWatch[$id]->acquire()){
$object = $this->leakWatch[$id]->get();
$this->leakWatch[$id]->release();
$valid = true;
$references = getReferenceCount($object, false);
}
return [
"id" => $id,
"class" => $this->leakInfo[$id]["class"],
"hash" => $this->leakInfo[$id]["hash"],
"valid" => $valid,
"references" => $references,
"object" => $includeObject ? $object : null
];
}
}

View File

@ -176,6 +176,8 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
*/
public $loginData = [];
public $creationTime = 0;
protected $randomClientId;
protected $uuid;
@ -499,6 +501,8 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
$this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0);
$this->uuid = Utils::dataToUUID($ip, $port, $clientID);
$this->creationTime = microtime(true);
}
/**
@ -2703,7 +2707,10 @@ class Player extends Human implements CommandSender, InventoryHolder, IPlayer{
unset($this->buffer);
}
$this->perm->clearPermissions();
if($this->perm !== null){
$this->perm->clearPermissions();
}
$this->server->removePlayer($this);
}

View File

@ -465,21 +465,8 @@ namespace pocketmine {
$logger->info("Stopping other threads");
foreach(ThreadManager::getInstance()->getAll() as $id => $thread){
if($thread->isRunning()){
$logger->debug("Killing " . (new \ReflectionClass($thread))->getShortName() . " thread");
$thread->kill();
sleep(1);
$thread->detach();
}elseif(!$thread->isJoined()){
if(!$thread->isTerminated()){
$logger->debug("Joining " . (new \ReflectionClass($thread))->getShortName() . " thread");
$thread->join();
}else{
$logger->debug("Killing " . (new \ReflectionClass($thread))->getShortName() . " thread");
$thread->kill();
$thread->detach();
}
}
$logger->debug("Stopping " . (new \ReflectionClass($thread))->getShortName() . " thread");
$thread->quit();
}
$logger->shutdown();

View File

@ -1570,10 +1570,10 @@ class Server{
if(($poolSize = $this->getProperty("settings.async-workers", "auto")) === "auto"){
$poolSize = ServerScheduler::$WORKERS;
$processors = Utils::getCoreCount();
$processors = Utils::getCoreCount() - 2;
if($processors > 0){
$poolSize = max(2, $processors);
$poolSize = max(1, $processors);
}
}
@ -1957,8 +1957,10 @@ class Server{
$this->reloadWhitelist();
$this->operators->reload();
$this->memoryManager->doObjectCleanup();
foreach($this->getIPBans()->getEntries() as $entry){
$this->blockAddress($entry->getName(), -1);
$this->getNetwork()->blockAddress($entry->getName(), -1);
}
$this->pluginManager->registerInterface(PharPluginLoader::class);
@ -2024,6 +2026,8 @@ class Server{
$interface->shutdown();
$this->network->unregisterInterface($interface);
}
$this->memoryManager->doObjectCleanup();
}catch(\Exception $e){
$this->logger->emergency("Crashed while crashing, killing process");
@kill(getmypid());

View File

@ -63,4 +63,25 @@ abstract class Thread extends \Thread{
return false;
}
/**
* Stops the thread using the best way possible. Try to stop it yourself before calling this.
*/
public function quit(){
if($this->isRunning()){
$this->kill();
$this->detach();
}elseif(!$this->isJoined()){
if(!$this->isTerminated()){
$this->join();
}else{
$this->kill();
$this->detach();
}
}else{
$this->detach();
}
ThreadManager::getInstance()->remove($this);
}
}

View File

@ -63,4 +63,26 @@ abstract class Worker extends \Worker{
return false;
}
/**
* Stops the thread using the best way possible. Try to stop it yourself before calling this.
*/
public function quit(){
if($this->isRunning()){
$this->unstack();
$this->kill();
$this->detach();
}elseif(!$this->isJoined()){
if(!$this->isTerminated()){
$this->join();
}else{
$this->kill();
$this->detach();
}
}else{
$this->detach();
}
ThreadManager::getInstance()->remove($this);
}
}

View File

@ -82,7 +82,7 @@ abstract class Entity extends Location implements Metadatable{
const DATA_AIR = 1;
const DATA_SHOW_NAMETAG = 3;
const DATA_POTION_COLOR = 7;
const DATA_POTION_VISIBLE = 8;
const DATA_POTION_AMBIENT = 8;
const DATA_NO_AI = 15;
@ -337,10 +337,10 @@ abstract class Entity extends Location implements Metadatable{
$b = ($color[2] / $count) & 0xff;
$this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, ($r << 16) + ($g << 8) + $b);
$this->setDataProperty(Entity::DATA_POTION_VISIBLE, Entity::DATA_TYPE_BYTE, $ambient ? 1 : 0);
$this->setDataProperty(Entity::DATA_POTION_AMBIENT, Entity::DATA_TYPE_BYTE, $ambient ? 1 : 0);
}else{
$this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, 0);
$this->setDataProperty(Entity::DATA_POTION_VISIBLE, Entity::DATA_TYPE_BYTE, 0);
$this->setDataProperty(Entity::DATA_POTION_AMBIENT, Entity::DATA_TYPE_BYTE, 0);
}
}

View File

@ -98,9 +98,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
$this->identifiers->detach($player);
unset($this->players[$identifier]);
unset($this->identifiersACK[$identifier]);
if(!$player->closed){
$player->close($player->getLeaveMessage(), $reason);
}
$player->close($player->getLeaveMessage(), $reason);
}
}

View File

@ -459,8 +459,9 @@ class PluginManager{
$subs = [];
foreach($this->permSubs[$permission] as $k => $perm){
/** @var \WeakRef $perm */
if($perm->valid()){
if($perm->acquire()){
$subs[] = $perm->get();
$perm->release();
}else{
unset($this->permSubs[$permission][$k]);
}
@ -507,8 +508,9 @@ class PluginManager{
if($op === true){
foreach($this->defSubsOp as $k => $perm){
/** @var \WeakRef $perm */
if($perm->valid()){
if($perm->acquire()){
$subs[] = $perm->get();
$perm->release();
}else{
unset($this->defSubsOp[$k]);
}
@ -516,8 +518,9 @@ class PluginManager{
}else{
foreach($this->defSubs as $k => $perm){
/** @var \WeakRef $perm */
if($perm->valid()){
if($perm->acquire()){
$subs[] = $perm->get();
$perm->release();
}else{
unset($this->defSubs[$k]);
}

View File

@ -117,8 +117,6 @@ class AsyncPool{
unset($this->taskWorkers[$task->getTaskId()]);
$task->cleanObject();
unset($task);
}
public function removeTasks(){