|
@@ -15,7 +15,8 @@ from zlib import crc32
|
|
|
|
|
|
import msgpack
|
|
|
from .constants import * # NOQA
|
|
|
-from .helpers import Error, ErrorWithTraceback, IntegrityError, Location, ProgressIndicatorPercent, bin_to_hex
|
|
|
+from .helpers import Error, ErrorWithTraceback, IntegrityError, InternalOSError, Location, ProgressIndicatorPercent, \
|
|
|
+ bin_to_hex
|
|
|
from .hashindex import NSIndex
|
|
|
from .locking import UpgradableLock, LockError, LockErrorT
|
|
|
from .lrucache import LRUCache
|
|
@@ -178,7 +179,7 @@ class Repository:
|
|
|
else:
|
|
|
return None
|
|
|
|
|
|
- def get_transaction_id(self):
|
|
|
+ def check_transaction(self):
|
|
|
index_transaction_id = self.get_index_transaction_id()
|
|
|
segments_transaction_id = self.io.get_segments_transaction_id()
|
|
|
if index_transaction_id is not None and segments_transaction_id is None:
|
|
@@ -191,6 +192,9 @@ class Repository:
|
|
|
else:
|
|
|
replay_from = index_transaction_id
|
|
|
self.replay_segments(replay_from, segments_transaction_id)
|
|
|
+
|
|
|
+ def get_transaction_id(self):
|
|
|
+ self.check_transaction()
|
|
|
return self.get_index_transaction_id()
|
|
|
|
|
|
def break_lock(self):
|
|
@@ -231,10 +235,27 @@ class Repository:
|
|
|
self.write_index()
|
|
|
self.rollback()
|
|
|
|
|
|
- def open_index(self, transaction_id):
|
|
|
+ def open_index(self, transaction_id, auto_recover=True):
|
|
|
if transaction_id is None:
|
|
|
return NSIndex()
|
|
|
- return NSIndex.read((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 error:
|
|
|
+ assert str(error) == 'hashindex_read failed' # everything else means we're in *deep* trouble
|
|
|
+ logger.warning('Repository index missing or corrupted, trying to recover')
|
|
|
+ try:
|
|
|
+ os.unlink(index_path)
|
|
|
+ except OSError as e:
|
|
|
+ raise InternalOSError(e) from None
|
|
|
+ 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(e) from None
|
|
|
|
|
|
def prepare_txn(self, transaction_id, do_cleanup=True):
|
|
|
self._active_txn = True
|
|
@@ -247,15 +268,33 @@ class Repository:
|
|
|
self._active_txn = False
|
|
|
raise
|
|
|
if not self.index or transaction_id is None:
|
|
|
- self.index = self.open_index(transaction_id)
|
|
|
+ try:
|
|
|
+ self.index = self.open_index(transaction_id, False)
|
|
|
+ except RuntimeError:
|
|
|
+ self.check_transaction()
|
|
|
+ self.index = self.open_index(transaction_id, False)
|
|
|
if transaction_id is None:
|
|
|
self.segments = {} # XXX bad name: usage_count_of_segment_x = self.segments[x]
|
|
|
self.compact = FreeSpace() # XXX bad name: freeable_space_of_segment_x = self.compact[x]
|
|
|
else:
|
|
|
if do_cleanup:
|
|
|
self.io.cleanup(transaction_id)
|
|
|
- with open(os.path.join(self.path, 'hints.%d' % transaction_id), 'rb') as fd:
|
|
|
- hints = msgpack.unpack(fd)
|
|
|
+ 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(hints_path, 'rb') as fd:
|
|
|
+ hints = msgpack.unpack(fd)
|
|
|
+ except (msgpack.UnpackException, msgpack.ExtraData, FileNotFoundError) as e:
|
|
|
+ logger.warning('Repository hints file missing or corrupted, trying to recover')
|
|
|
+ if not isinstance(e, FileNotFoundError):
|
|
|
+ os.unlink(hints_path)
|
|
|
+ # index must exist at this point
|
|
|
+ os.unlink(index_path)
|
|
|
+ self.check_transaction()
|
|
|
+ self.prepare_txn(transaction_id)
|
|
|
+ return
|
|
|
+ except OSError as os_error:
|
|
|
+ raise InternalOSError(os_error) from None
|
|
|
if hints[b'version'] == 1:
|
|
|
logger.debug('Upgrading from v1 hints.%d', transaction_id)
|
|
|
self.segments = hints[b'segments']
|