From cd80ae00d428c67fae31bc1ee9819d5e4f859d42 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 29 Dec 2018 16:37:10 +0000 Subject: [PATCH 1/3] Handle errors properly on chunk load Only CorruptedChunkException and UnsupportedChunkFormatException are expected. Anything else should crash the server. --- src/pocketmine/level/Level.php | 7 ++++--- .../level/format/io/BaseLevelProvider.php | 18 ++++++++++++++++++ .../level/format/io/LevelProvider.php | 6 ++++-- .../level/format/io/region/Anvil.php | 4 ++-- .../level/format/io/region/McRegion.php | 13 +++++++++++-- 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 10a42c544..da6ef5cab 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -49,6 +49,8 @@ use pocketmine\level\format\ChunkException; use pocketmine\level\format\EmptySubChunk; use pocketmine\level\format\io\BaseLevelProvider; use pocketmine\level\format\io\ChunkRequestTask; +use pocketmine\level\format\io\exception\CorruptedChunkException; +use pocketmine\level\format\io\exception\UnsupportedChunkFormatException; use pocketmine\level\format\io\LevelProvider; use pocketmine\level\generator\Generator; use pocketmine\level\generator\GeneratorManager; @@ -2750,10 +2752,9 @@ class Level implements ChunkManager, Metadatable{ try{ $chunk = $this->provider->loadChunk($x, $z); - }catch(\Exception $e){ + }catch(CorruptedChunkException | UnsupportedChunkFormatException $e){ $logger = $this->server->getLogger(); - $logger->critical("An error occurred while loading chunk x=$x z=$z: " . $e->getMessage()); - $logger->logException($e); + $logger->critical("Failed to load chunk x=$x z=$z: " . $e->getMessage()); } if($chunk === null and $create){ diff --git a/src/pocketmine/level/format/io/BaseLevelProvider.php b/src/pocketmine/level/format/io/BaseLevelProvider.php index 574c519a6..2ff6fd659 100644 --- a/src/pocketmine/level/format/io/BaseLevelProvider.php +++ b/src/pocketmine/level/format/io/BaseLevelProvider.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace pocketmine\level\format\io; use pocketmine\level\format\Chunk; +use pocketmine\level\format\io\exception\CorruptedChunkException; +use pocketmine\level\format\io\exception\UnsupportedChunkFormatException; use pocketmine\level\LevelException; use pocketmine\math\Vector3; use pocketmine\nbt\BigEndianNBTStream; @@ -152,6 +154,14 @@ abstract class BaseLevelProvider implements LevelProvider{ file_put_contents($this->getPath() . "level.dat", $buffer); } + /** + * @param int $chunkX + * @param int $chunkZ + * + * @return Chunk|null + * @throws CorruptedChunkException + * @throws UnsupportedChunkFormatException + */ public function loadChunk(int $chunkX, int $chunkZ) : ?Chunk{ return $this->readChunk($chunkX, $chunkZ); } @@ -163,6 +173,14 @@ abstract class BaseLevelProvider implements LevelProvider{ $this->writeChunk($chunk); } + /** + * @param int $chunkX + * @param int $chunkZ + * + * @return Chunk|null + * @throws UnsupportedChunkFormatException + * @throws CorruptedChunkException + */ abstract protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk; abstract protected function writeChunk(Chunk $chunk) : void; diff --git a/src/pocketmine/level/format/io/LevelProvider.php b/src/pocketmine/level/format/io/LevelProvider.php index e00d1c5e9..31a71bbe1 100644 --- a/src/pocketmine/level/format/io/LevelProvider.php +++ b/src/pocketmine/level/format/io/LevelProvider.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace pocketmine\level\format\io; use pocketmine\level\format\Chunk; +use pocketmine\level\format\io\exception\CorruptedChunkException; +use pocketmine\level\format\io\exception\UnsupportedChunkFormatException; use pocketmine\math\Vector3; interface LevelProvider{ @@ -100,8 +102,8 @@ interface LevelProvider{ * * @return null|Chunk * - * @throws \Exception any of a range of exceptions that could be thrown while reading chunks. See individual - * implementations for details. + * @throws CorruptedChunkException + * @throws UnsupportedChunkFormatException */ public function loadChunk(int $chunkX, int $chunkZ) : ?Chunk; diff --git a/src/pocketmine/level/format/io/region/Anvil.php b/src/pocketmine/level/format/io/region/Anvil.php index 9cd69599f..12b1cc965 100644 --- a/src/pocketmine/level/format/io/region/Anvil.php +++ b/src/pocketmine/level/format/io/region/Anvil.php @@ -24,8 +24,8 @@ declare(strict_types=1); namespace pocketmine\level\format\io\region; use pocketmine\level\format\Chunk; -use pocketmine\level\format\ChunkException; use pocketmine\level\format\io\ChunkUtils; +use pocketmine\level\format\io\exception\CorruptedChunkException; use pocketmine\level\format\SubChunk; use pocketmine\nbt\BigEndianNBTStream; use pocketmine\nbt\NBT; @@ -99,7 +99,7 @@ class Anvil extends McRegion{ $nbt = new BigEndianNBTStream(); $chunk = $nbt->readCompressed($data); if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){ - throw new ChunkException("Invalid NBT format"); + throw new CorruptedChunkException("'Level' key is missing from chunk NBT"); } $chunk = $chunk->getCompoundTag("Level"); diff --git a/src/pocketmine/level/format/io/region/McRegion.php b/src/pocketmine/level/format/io/region/McRegion.php index c080b79a4..6b2920c95 100644 --- a/src/pocketmine/level/format/io/region/McRegion.php +++ b/src/pocketmine/level/format/io/region/McRegion.php @@ -24,9 +24,9 @@ declare(strict_types=1); namespace pocketmine\level\format\io\region; use pocketmine\level\format\Chunk; -use pocketmine\level\format\ChunkException; use pocketmine\level\format\io\BaseLevelProvider; use pocketmine\level\format\io\ChunkUtils; +use pocketmine\level\format\io\exception\CorruptedChunkException; use pocketmine\level\format\SubChunk; use pocketmine\level\generator\GeneratorManager; use pocketmine\level\Level; @@ -107,12 +107,13 @@ class McRegion extends BaseLevelProvider{ * @param string $data * * @return Chunk + * @throws CorruptedChunkException */ protected function nbtDeserialize(string $data) : Chunk{ $nbt = new BigEndianNBTStream(); $chunk = $nbt->readCompressed($data); if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){ - throw new ChunkException("Invalid NBT format"); + throw new CorruptedChunkException("'Level' key is missing from chunk NBT"); } $chunk = $chunk->getCompoundTag("Level"); @@ -348,6 +349,14 @@ class McRegion extends BaseLevelProvider{ } } + /** + * @param int $chunkX + * @param int $chunkZ + * + * @return Chunk|null + * + * @throws CorruptedChunkException + */ protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk{ $regionX = $regionZ = null; self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); From 5ecc5ed7e0d5d2f435142578bbc65be494becfca Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 29 Dec 2018 16:37:59 +0000 Subject: [PATCH 2/3] Get rid of catch-all on chunk unload god only knows what the fuck is going on in here that warrants this catch-all... so let's remove it and find out! --- src/pocketmine/level/Level.php | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index da6ef5cab..ee1935fa0 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -2835,23 +2835,17 @@ class Level implements ChunkManager, Metadatable{ return false; } - try{ - if($trySave and $this->getAutoSave() and $chunk->isGenerated()){ - if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0){ - $this->provider->saveChunk($chunk); - } + if($trySave and $this->getAutoSave() and $chunk->isGenerated()){ + if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0){ + $this->provider->saveChunk($chunk); } - - foreach($this->getChunkLoaders($x, $z) as $loader){ - $loader->onChunkUnloaded($chunk); - } - - $chunk->onUnload(); - }catch(\Throwable $e){ - $logger = $this->server->getLogger(); - $logger->error($this->server->getLanguage()->translateString("pocketmine.level.chunkUnloadError", [$e->getMessage()])); - $logger->logException($e); } + + foreach($this->getChunkLoaders($x, $z) as $loader){ + $loader->onChunkUnloaded($chunk); + } + + $chunk->onUnload(); } unset($this->chunks[$chunkHash]); From 3f5e83a32294f0f291e2cda60575481ad89ffe51 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 28 Dec 2018 23:03:24 +0000 Subject: [PATCH 3/3] Backport 23954c4cda739190cb79295eeb78859c5c329be0 to 3.5 branch --- .../level/format/io/region/RegionLoader.php | 75 ------------------- 1 file changed, 75 deletions(-) diff --git a/src/pocketmine/level/format/io/region/RegionLoader.php b/src/pocketmine/level/format/io/region/RegionLoader.php index 15b868bf1..082476a93 100644 --- a/src/pocketmine/level/format/io/region/RegionLoader.php +++ b/src/pocketmine/level/format/io/region/RegionLoader.php @@ -198,81 +198,6 @@ class RegionLoader{ } } - public function doSlowCleanUp() : int{ - for($i = 0; $i < 1024; ++$i){ - if($this->locationTable[$i][0] === 0 or $this->locationTable[$i][1] === 0){ - continue; - } - fseek($this->filePointer, $this->locationTable[$i][0] << 12); - $chunk = fread($this->filePointer, $this->locationTable[$i][1] << 12); - $length = Binary::readInt(substr($chunk, 0, 4)); - if($length <= 1){ - $this->locationTable[$i] = [0, 0, 0]; //Non-generated chunk, remove it from index - } - - try{ - $chunk = zlib_decode(substr($chunk, 5)); - }catch(\Throwable $e){ - $this->locationTable[$i] = [0, 0, 0]; //Corrupted chunk, remove it - continue; - } - - $chunk = chr(self::COMPRESSION_ZLIB) . zlib_encode($chunk, ZLIB_ENCODING_DEFLATE, 9); - $chunk = Binary::writeInt(strlen($chunk)) . $chunk; - $sectors = (int) ceil(strlen($chunk) / 4096); - if($sectors > $this->locationTable[$i][1]){ - $this->locationTable[$i][0] = $this->lastSector + 1; - $this->lastSector += $sectors; - } - fseek($this->filePointer, $this->locationTable[$i][0] << 12); - fwrite($this->filePointer, str_pad($chunk, $sectors << 12, "\x00", STR_PAD_RIGHT)); - } - $this->writeLocationTable(); - $n = $this->cleanGarbage(); - $this->writeLocationTable(); - - return $n; - } - - private function cleanGarbage() : int{ - $sectors = []; - foreach($this->locationTable as $index => $data){ //Calculate file usage - if($data[0] === 0 or $data[1] === 0){ - $this->locationTable[$index] = [0, 0, 0]; - continue; - } - for($i = 0; $i < $data[1]; ++$i){ - $sectors[$data[0]] = $index; - } - } - - if(count($sectors) === ($this->lastSector - 2)){ //No collection needed - return 0; - } - - ksort($sectors); - $shift = 0; - $lastSector = 1; //First chunk - 1 - - fseek($this->filePointer, 8192); - $sector = 2; - foreach($sectors as $sector => $index){ - if(($sector - $lastSector) > 1){ - $shift += $sector - $lastSector - 1; - } - if($shift > 0){ - fseek($this->filePointer, $sector << 12); - $old = fread($this->filePointer, 4096); - fseek($this->filePointer, ($sector - $shift) << 12); - fwrite($this->filePointer, $old, 4096); - } - $this->locationTable[$index][0] -= $shift; - $lastSector = $sector; - } - ftruncate($this->filePointer, ($sector + 1) << 12); //Truncate to the end of file written - return $shift; - } - protected function loadLocationTable(){ fseek($this->filePointer, 0); $this->lastSector = 1;