Added better error handling for corrupt chunks
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
region = self._get_regionobj(regionfile)
|
# Try 3 times to load and parse this chunk before giving up and raising
|
||||||
data = region.load_chunk(x, z)
|
# 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:
|
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
|
||||||
s += obj.regiondir
|
try:
|
||||||
|
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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user