0

Added better error handling for corrupt chunks

This commit is contained in:
Andrew Brown
2012-03-17 22:41:11 -04:00
parent 9b7226e91e
commit e9cf747513
4 changed files with 71 additions and 10 deletions

View File

@@ -59,10 +59,10 @@ class LRUCache(object):
""" """
self.cache = {} self.cache = {}
self.listhead = LRUCache._LinkNode()
self.listtail = LRUCache._LinkNode()
# Two sentinel nodes at the ends of the linked list simplify boundary # Two sentinel nodes at the ends of the linked list simplify boundary
# conditions in the code below. # conditions in the code below.
self.listhead = LRUCache._LinkNode()
self.listtail = LRUCache._LinkNode()
self.listhead.right = self.listtail self.listhead.right = self.listtail
self.listtail.left = self.listhead self.listtail.left = self.listhead
@@ -125,6 +125,18 @@ class LRUCache(object):
cache[key] = link 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 # memcached is an option, but unless your IO costs are really high, it just
# ends up adding overhead and isn't worth it. # ends up adding overhead and isn't worth it.
try: try:

View File

@@ -180,11 +180,15 @@ class NBTFileReader(object):
except (struct.error, ValueError), e: except (struct.error, ValueError), e:
raise CorruptNBTError("could not parse nbt: %s" % (str(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 """An exception raised when the MCRFileReader class encounters an
error during region file parsing. error during region file parsing.
""" """
pass pass
class CorruptChunkError(CorruptionError):
pass
# For reference, the MCR format is outlined at # For reference, the MCR format is outlined at
# <http://www.minecraftwiki.net/wiki/Beta_Level_Format> # <http://www.minecraftwiki.net/wiki/Beta_Level_Format>
@@ -294,4 +298,7 @@ class MCRFileReader(object):
raise CorruptRegionError("chunk length is invalid") raise CorruptRegionError("chunk length is invalid")
data = StringIO.StringIO(data) data = StringIO.StringIO(data)
try:
return NBTFileReader(data, is_gzip=is_gzip).read_all() return NBTFileReader(data, is_gzip=is_gzip).read_all()
except Exception, e:
raise CorruptChunkError("Misc error parsing chunk: " + str(e))

View File

@@ -29,6 +29,7 @@ from itertools import product, izip
from PIL import Image from PIL import Image
from .util import roundrobin from .util import roundrobin
from . import nbt
from .files import FileReplacer from .files import FileReplacer
from .optimizeimages import optimize_image from .optimizeimages import optimize_image
import c_overviewer import c_overviewer
@@ -934,9 +935,17 @@ class TileSet(object):
max_chunk_mtime = chunk_mtime max_chunk_mtime = chunk_mtime
# draw the chunk! # draw the chunk!
c_overviewer.render_loop(self.regionset, chunkx, chunky, chunkz, try:
tileimg, xpos, ypos, self.options['rendermode'], c_overviewer.render_loop(self.regionset, chunkx, chunky,
self.textures) 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 ## Semi-handy routine for debugging the drawing routine
## Draw the outline of the top of the chunk ## Draw the outline of the top of the chunk

View File

@@ -279,6 +279,8 @@ class RegionSet(object):
raise Exception("Woah, what kind of dimension is this?! %r" % self.regiondir) raise Exception("Woah, what kind of dimension is this?! %r" % self.regiondir)
def _get_regionobj(self, regionfilename): 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: try:
return self.regioncache[regionfilename] return self.regioncache[regionfilename]
except KeyError: except KeyError:
@@ -317,8 +319,36 @@ class RegionSet(object):
if regionfile is None: if regionfile is None:
raise ChunkDoesntExist("Chunk %s,%s doesn't exist (and neither does its region)" % (x,z)) raise ChunkDoesntExist("Chunk %s,%s doesn't exist (and neither does its region)" % (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) region = self._get_regionobj(regionfile)
data = region.load_chunk(x, z) 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: if data is None:
raise ChunkDoesntExist("Chunk %s,%s doesn't exist" % (x,z)) raise ChunkDoesntExist("Chunk %s,%s doesn't exist" % (x,z))
@@ -599,7 +629,10 @@ class CachedRegionSet(RegionSetWrapper):
s += obj.__class__.__name__ + "." s += obj.__class__.__name__ + "."
obj = obj._r obj = obj._r
# obj should now be the actual RegionSet object # obj should now be the actual RegionSet object
try:
s += obj.regiondir s += obj.regiondir
except AttributeError:
s += repr(obj)
logging.debug("Initializing a cache with key '%s'", s) logging.debug("Initializing a cache with key '%s'", s)