From e9cf7475137f037a8a69dfdbb256cdc60f04af53 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sat, 17 Mar 2012 22:41:11 -0400 Subject: [PATCH] Added better error handling for corrupt chunks --- overviewer_core/cache.py | 16 ++++++++++++++-- overviewer_core/nbt.py | 11 +++++++++-- overviewer_core/tileset.py | 15 ++++++++++++--- overviewer_core/world.py | 39 +++++++++++++++++++++++++++++++++++--- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/overviewer_core/cache.py b/overviewer_core/cache.py index fb6d8be..6c5a5a2 100644 --- a/overviewer_core/cache.py +++ b/overviewer_core/cache.py @@ -59,10 +59,10 @@ class LRUCache(object): """ self.cache = {} - self.listhead = LRUCache._LinkNode() - self.listtail = LRUCache._LinkNode() # Two sentinel nodes at the ends of the linked list simplify boundary # conditions in the code below. + self.listhead = LRUCache._LinkNode() + self.listtail = LRUCache._LinkNode() self.listhead.right = self.listtail self.listtail.left = self.listhead @@ -125,6 +125,18 @@ class LRUCache(object): cache[key] = link + def __delitem__(self, key): + # Used to flush the cache of this key + link = self.cache[key] + del cache[key] + link.left.right = link.right + link.right.left = link.left + + # Call the destructor + d = self.destructor + if d: + d(link.value) + # memcached is an option, but unless your IO costs are really high, it just # ends up adding overhead and isn't worth it. try: diff --git a/overviewer_core/nbt.py b/overviewer_core/nbt.py index 4420673..faf94bf 100644 --- a/overviewer_core/nbt.py +++ b/overviewer_core/nbt.py @@ -180,11 +180,15 @@ class NBTFileReader(object): except (struct.error, ValueError), e: raise CorruptNBTError("could not parse nbt: %s" % (str(e),)) -class CorruptRegionError(Exception): +class CorruptionError(Exception): + pass +class CorruptRegionError(CorruptionError): """An exception raised when the MCRFileReader class encounters an error during region file parsing. """ pass +class CorruptChunkError(CorruptionError): + pass # For reference, the MCR format is outlined at # @@ -294,4 +298,7 @@ class MCRFileReader(object): raise CorruptRegionError("chunk length is invalid") data = StringIO.StringIO(data) - return NBTFileReader(data, is_gzip=is_gzip).read_all() + try: + return NBTFileReader(data, is_gzip=is_gzip).read_all() + except Exception, e: + raise CorruptChunkError("Misc error parsing chunk: " + str(e)) diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 8f78c97..b9a9dd6 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -29,6 +29,7 @@ from itertools import product, izip from PIL import Image from .util import roundrobin +from . import nbt from .files import FileReplacer from .optimizeimages import optimize_image import c_overviewer @@ -934,9 +935,17 @@ class TileSet(object): max_chunk_mtime = chunk_mtime # draw the chunk! - c_overviewer.render_loop(self.regionset, chunkx, chunky, chunkz, - tileimg, xpos, ypos, self.options['rendermode'], - self.textures) + try: + c_overviewer.render_loop(self.regionset, chunkx, chunky, + chunkz, tileimg, xpos, ypos, + self.options['rendermode'], self.textures) + except nbt.CorruptionError: + # A warning and traceback was already printed by world.py's + # get_chunk() + logging.debug("Skipping corrupt chunk at %s,%s", chunkx, chunkz) + except Exception, e: + logging.warning("Could not render chunk %s,%s for some reason. I'm going to ignore this and continue", chunkx, chunkz) + logging.debug("Full error was:", exc_info=1) ## Semi-handy routine for debugging the drawing routine ## Draw the outline of the top of the chunk diff --git a/overviewer_core/world.py b/overviewer_core/world.py index 9c11282..f8cbc57 100644 --- a/overviewer_core/world.py +++ b/overviewer_core/world.py @@ -279,6 +279,8 @@ class RegionSet(object): raise Exception("Woah, what kind of dimension is this?! %r" % self.regiondir) def _get_regionobj(self, regionfilename): + # Check the cache first. If it's not there, create the + # nbt.MCRFileReader object, cache it, and return it try: return self.regioncache[regionfilename] except KeyError: @@ -317,8 +319,36 @@ class RegionSet(object): if regionfile is None: raise ChunkDoesntExist("Chunk %s,%s doesn't exist (and neither does its region)" % (x,z)) - region = self._get_regionobj(regionfile) - data = region.load_chunk(x, z) + # Try 3 times to load and parse this chunk before giving up and raising + # an error + tries = 5 + while True: + try: + region = self._get_regionobj(regionfile) + data = region.load_chunk(x, z) + except nbt.CorruptionError, e: + tries -= 1 + if tries > 0: + # Flush the region cache to possibly read a new region file + # header + logging.debug("Encountered a corrupt chunk at %s,%s. Flushing cache and retrying", x, z) + logging.debug("Error was:", exc_info=1) + del self.regioncache[regionfile] + time.sleep(0.5) + continue + else: + logging.warning("Tried %d times to read chunk %d,%d but I got error %s", + tries, x, z, e) + logging.debug("Full traceback:", exc_info=1) + # Let this exception propagate out through the C code into + # tileset.py, where it is caught and gracefully continues + # with the next chunk + raise + else: + # no exception raised: break out of the loop + break + + if data is None: raise ChunkDoesntExist("Chunk %s,%s doesn't exist" % (x,z)) @@ -599,7 +629,10 @@ class CachedRegionSet(RegionSetWrapper): s += obj.__class__.__name__ + "." obj = obj._r # obj should now be the actual RegionSet object - s += obj.regiondir + try: + s += obj.regiondir + except AttributeError: + s += repr(obj) logging.debug("Initializing a cache with key '%s'", s)