44    private const DEFAULT_CHECK_RATE = Server::TARGET_TICKS_PER_SECOND;
 
   45    private const DEFAULT_CONTINUOUS_TRIGGER_RATE = Server::TARGET_TICKS_PER_SECOND * 2;
 
   46    private const DEFAULT_TICKS_PER_GC = 30 * 60 * Server::TARGET_TICKS_PER_SECOND;
 
   50    private int $memoryLimit;
 
   51    private int $globalMemoryLimit;
 
   52    private int $checkRate;
 
   53    private int $checkTicker = 0;
 
   54    private bool $lowMemory = 
false;
 
   56    private bool $continuousTrigger = 
true;
 
   57    private int $continuousTriggerRate;
 
   58    private int $continuousTriggerCount = 0;
 
   59    private int $continuousTriggerTicker = 0;
 
   61    private int $garbageCollectionPeriod;
 
   62    private int $garbageCollectionTicker = 0;
 
   64    private int $lowMemChunkRadiusOverride;
 
   66    private bool $dumpWorkers = 
true;
 
   68    private \Logger $logger;
 
   70    public function __construct(
 
   73        $this->logger = new \PrefixedLogger($server->getLogger(), 
"Memory Manager");
 
   76        $this->init($server->getConfigGroup());
 
   80        $this->memoryLimit = $config->getPropertyInt(Yml::MEMORY_MAIN_LIMIT, 0) * 1024 * 1024;
 
   82        $defaultMemory = 1024;
 
   84        if(preg_match(
"/([0-9]+)([KMGkmg])/", $config->getConfigString(
"memory-limit", 
""), $matches) > 0){
 
   85            $m = (int) $matches[1];
 
   89                $defaultMemory = match(mb_strtoupper($matches[2])){
 
   90                    "K" => intdiv($m, 1024),
 
   98        $hardLimit = $config->getPropertyInt(Yml::MEMORY_MAIN_HARD_LIMIT, $defaultMemory);
 
  101            ini_set(
"memory_limit", 
'-1');
 
  103            ini_set(
"memory_limit", $hardLimit . 
"M");
 
  106        $this->globalMemoryLimit = $config->getPropertyInt(Yml::MEMORY_GLOBAL_LIMIT, 0) * 1024 * 1024;
 
  107        $this->checkRate = $config->getPropertyInt(Yml::MEMORY_CHECK_RATE, self::DEFAULT_CHECK_RATE);
 
  108        $this->continuousTrigger = $config->getPropertyBool(Yml::MEMORY_CONTINUOUS_TRIGGER, 
true);
 
  109        $this->continuousTriggerRate = $config->getPropertyInt(Yml::MEMORY_CONTINUOUS_TRIGGER_RATE, self::DEFAULT_CONTINUOUS_TRIGGER_RATE);
 
  111        $this->garbageCollectionPeriod = $config->getPropertyInt(Yml::MEMORY_GARBAGE_COLLECTION_PERIOD, self::DEFAULT_TICKS_PER_GC);
 
  113        $this->lowMemChunkRadiusOverride = $config->getPropertyInt(Yml::MEMORY_MAX_CHUNKS_CHUNK_RADIUS, 4);
 
  115        $this->dumpWorkers = $config->getPropertyBool(Yml::MEMORY_MEMORY_DUMP_DUMP_ASYNC_WORKER, 
true);
 
  118    public function isLowMemory() : 
bool{
 
  119        return $this->lowMemory;
 
  122    public function getGlobalMemoryLimit() : 
int{
 
  123        return $this->globalMemoryLimit;
 
  130        return !$this->lowMemory;
 
 
  137        return ($this->lowMemory && $this->lowMemChunkRadiusOverride > 0) ? min($this->lowMemChunkRadiusOverride, $distance) : $distance;
 
 
  143    public function trigger(
int $memory, 
int $limit, 
bool $global = 
false, 
int $triggerCount = 0) : void{
 
  144        $this->logger->debug(sprintf(
"%sLow memory triggered, limit %gMB, using %gMB",
 
  145            $global ? 
"Global " : 
"", round(($limit / 1024) / 1024, 2), round(($memory / 1024) / 1024, 2)));
 
  146        foreach($this->
server->getWorldManager()->getWorlds() as $world){
 
  147            $world->clearCache(true);
 
  149        ChunkCache::pruneCaches();
 
  151        foreach($this->server->getWorldManager()->getWorlds() as $world){
 
  152            $world->doChunkGarbageCollection();
 
  155        $ev = 
new LowMemoryEvent($memory, $limit, $global, $triggerCount);
 
  158        $cycles = $this->triggerGarbageCollector();
 
  160        $this->logger->debug(sprintf(
"Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2)));
 
 
  167        Timings::$memoryManager->startTiming();
 
  169        if(($this->memoryLimit > 0 || $this->globalMemoryLimit > 0) && ++$this->checkTicker >= $this->checkRate){
 
  170            $this->checkTicker = 0;
 
  171            $memory = Process::getAdvancedMemoryUsage();
 
  173            if($this->memoryLimit > 0 && $memory[0] > $this->memoryLimit){
 
  175            }elseif($this->globalMemoryLimit > 0 && $memory[1] > $this->globalMemoryLimit){
 
  179            if($trigger !== 
false){
 
  180                if($this->lowMemory && $this->continuousTrigger){
 
  181                    if(++$this->continuousTriggerTicker >= $this->continuousTriggerRate){
 
  182                        $this->continuousTriggerTicker = 0;
 
  183                        $this->trigger($memory[$trigger], $this->memoryLimit, $trigger > 0, ++$this->continuousTriggerCount);
 
  186                    $this->lowMemory = 
true;
 
  187                    $this->continuousTriggerCount = 0;
 
  188                    $this->trigger($memory[$trigger], $this->memoryLimit, $trigger > 0);
 
  191                $this->lowMemory = 
false;
 
  195        if($this->garbageCollectionPeriod > 0 && ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){
 
  196            $this->garbageCollectionTicker = 0;
 
  197            $this->triggerGarbageCollector();
 
  199            $this->cycleGcManager->maybeCollectCycles();
 
  202        Timings::$memoryManager->stopTiming();
 
 
  205    public function triggerGarbageCollector() : int{
 
  206        Timings::$garbageCollector->startTiming();
 
  208        $pool = $this->
server->getAsyncPool();
 
  209        if(($w = $pool->shutdownUnusedWorkers()) > 0){
 
  210            $this->logger->debug(
"Shut down $w idle async pool workers");
 
  212        foreach($pool->getRunningWorkers() as $i){
 
  213            $pool->submitTaskToWorker(new GarbageCollectionTask(), $i);
 
  216        $cycles = gc_collect_cycles();
 
  219        Timings::$garbageCollector->stopTiming();
 
  227    public function dumpServerMemory(
string $outputFolder, 
int $maxNesting, 
int $maxStringSize) : void{
 
  229        $logger->
notice(
"After the memory dump is done, the server might crash");
 
  230        MemoryDump::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger);
 
  232        if($this->dumpWorkers){
 
  233            $pool = $this->
server->getAsyncPool();
 
  234            foreach($pool->getRunningWorkers() as $i){
 
  235                $pool->submitTaskToWorker(
new DumpWorkerMemoryTask($outputFolder, $maxNesting, $maxStringSize), $i);
 
 
  245    public static function dumpMemory(mixed $startingObject, 
string $outputFolder, 
int $maxNesting, 
int $maxStringSize, \
Logger $logger) : void{
 
  246        MemoryDump::dumpMemory($startingObject, $outputFolder, $maxNesting, $maxStringSize, $logger);