mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-15 10:19:39 +00:00
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:
parent
7e539ec85a
commit
1a1b8830a4
@ -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
|
||||
];
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
if($this->perm !== null){
|
||||
$this->perm->clearPermissions();
|
||||
}
|
||||
|
||||
$this->server->removePlayer($this);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,11 +98,9 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function close(Player $player, $reason = "unknown reason"){
|
||||
if(isset($this->identifiers[$player])){
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -117,8 +117,6 @@ class AsyncPool{
|
||||
unset($this->taskWorkers[$task->getTaskId()]);
|
||||
|
||||
$task->cleanObject();
|
||||
|
||||
unset($task);
|
||||
}
|
||||
|
||||
public function removeTasks(){
|
||||
|
Loading…
x
Reference in New Issue
Block a user