Handle permission and similar errors on the index

This commit is contained in:
Marian Beermann 2016-04-22 11:40:16 +02:00
parent 252c1b9802
commit d979a84f37
3 changed files with 41 additions and 14 deletions

View file

@ -27,6 +27,14 @@ cdef extern from "_hashindex.c":
uint32_t _le32toh(uint32_t v)
cdef extern from "errno.h":
int errno
cdef extern from "string.h":
char *strerror(int errnum)
cdef _NoDefault = object()
"""
@ -63,6 +71,8 @@ cdef class IndexBase:
path = os.fsencode(path)
self.index = hashindex_read(path)
if not self.index:
if errno:
raise OSError(errno, strerror(errno), path)
raise RuntimeError('hashindex_read failed')
else:
self.index = hashindex_init(capacity, self.key_size, self.value_size)

View file

@ -238,20 +238,24 @@ class Repository:
def open_index(self, transaction_id, auto_recover=True):
if transaction_id is None:
return NSIndex()
index_path = (os.path.join(self.path, 'index.%d') % transaction_id).encode('utf-8')
index_path = os.path.join(self.path, 'index.%d' % transaction_id).encode('utf-8')
try:
return NSIndex.read(index_path)
except RuntimeError as re:
assert str(re) == 'hashindex_read failed' # everything else means we're in *deep* trouble
except RuntimeError as error:
assert str(error) == 'hashindex_read failed' # everything else means we're in *deep* trouble
# corrupted index file, need to replay segments
os.unlink(os.path.join(self.path, 'hints.%d' % transaction_id))
os.unlink(os.path.join(self.path, 'index.%d' % transaction_id))
try:
os.unlink(index_path)
except OSError as e:
raise InternalOSError from e
if not auto_recover:
raise
self.prepare_txn(self.get_transaction_id())
# don't leave an open transaction around
self.commit()
return self.open_index(self.get_transaction_id())
except OSError as e:
raise InternalOSError from e
def prepare_txn(self, transaction_id, do_cleanup=True):
self._active_txn = True
@ -275,15 +279,17 @@ class Repository:
else:
if do_cleanup:
self.io.cleanup(transaction_id)
hints_path = os.path.join(self.path, 'hints.%d' % transaction_id)
index_path = os.path.join(self.path, 'index.%d' % transaction_id)
try:
with open(os.path.join(self.path, 'hints.%d' % transaction_id), 'rb') as fd:
with open(hints_path, 'rb') as fd:
hints = msgpack.unpack(fd)
except (msgpack.UnpackException, msgpack.ExtraData, FileNotFoundError) as e:
# corrupted or deleted hints file, need to replay segments
if not isinstance(e, FileNotFoundError):
os.unlink(os.path.join(self.path, 'hints.%d' % transaction_id))
os.unlink(hints_path)
# index must exist at this point
os.unlink(os.path.join(self.path, 'index.%d' % transaction_id))
os.unlink(index_path)
self.check_transaction()
self.prepare_txn(transaction_id)
return

View file

@ -283,14 +283,18 @@ class RepositoryAuxiliaryCorruptionTestCase(RepositoryTestCaseBase):
self.repository.commit()
def test_corrupted_hints(self):
with open(os.path.join(self.repository.path, 'hints.0'), 'ab') as fp:
fp.write(b'123456789')
with open(os.path.join(self.repository.path, 'hints.0'), 'ab') as fd:
fd.write(b'123456789')
self.do_commit()
def test_deleted_hints(self):
os.unlink(os.path.join(self.repository.path, 'hints.0'))
self.do_commit()
def test_deleted_index(self):
os.unlink(os.path.join(self.repository.path, 'index.0'))
self.do_commit()
def test_unreadable_hints(self):
hints = os.path.join(self.repository.path, 'hints.0')
os.unlink(hints)
@ -299,16 +303,23 @@ class RepositoryAuxiliaryCorruptionTestCase(RepositoryTestCaseBase):
self.do_commit()
def test_index(self):
with open(os.path.join(self.repository.path, 'index.0'), 'wb') as fp:
fp.write(b'123456789')
with open(os.path.join(self.repository.path, 'index.0'), 'wb') as fd:
fd.write(b'123456789')
self.do_commit()
def test_index_outside_transaction(self):
with open(os.path.join(self.repository.path, 'index.0'), 'wb') as fp:
fp.write(b'123456789')
with open(os.path.join(self.repository.path, 'index.0'), 'wb') as fd:
fd.write(b'123456789')
with self.repository:
assert len(self.repository) == 1
def test_unreadable_index(self):
index = os.path.join(self.repository.path, 'index.0')
os.unlink(index)
os.mkdir(index)
with self.assert_raises(InternalOSError):
self.do_commit()
class RepositoryCheckTestCase(RepositoryTestCaseBase):