comparison hgsubversion/maps.py @ 1550:67b28d657f62

sqliterevmap: break ".hashes()" cycle in a safer way The `fromsvn()` revset implementation could cause weakref error when using sqliterevmap like: File "hgsubversion/util.py", line 357, in <lambda> return subset.filter(lambda r: tonode(r) in hashes) File "hgsubversion/maps.py", line 542, in __contains__ return self.get(key) != None File "hgsubversion/maps.py", line 533, in get for row in self.revmap._query( ReferenceError: weakly-referenced object no longer exists Basically the seemingly harmless assignment could break surprisingly: # dangerous: `hashes` does not have a reference of `meta.revmap` and may # become unavailable after `meta`, `revmap` being released by refcount. hashes = meta.revmap.hashes() The above syntax is nice to support while avoiding cycles is also nice. This patch removes `revmap._hashes` so the revmap no longer owns a reference of a `ReverseRevMap` object so the `ReverseRevMap` object no longer needs to use weakref for `self.revmap`. This could actually be caught by `comprehensive/test_sqlite_revmap.py`. I was not careful enough verifying the "fromsvn()" patch.
author Jun Wu <quark@fb.com>
date Thu, 21 Dec 2017 17:39:52 -0800
parents 09476d758b59
children cff81f35b31e
comparison
equal deleted inserted replaced
1549:8410a978c650 1550:67b28d657f62
521 sqlblobtype = bytes if sys.version_info >= (3, 0) else buffer 521 sqlblobtype = bytes if sys.version_info >= (3, 0) else buffer
522 522
523 class ReverseRevMap(object): 523 class ReverseRevMap(object):
524 # collections.Mapping is not suitable since we don't want 2/3 of 524 # collections.Mapping is not suitable since we don't want 2/3 of
525 # its required interfaces: __iter__, __len__. 525 # its required interfaces: __iter__, __len__.
526 def __init__(self, revmap): 526 def __init__(self, revmap, cache):
527 self.revmap = weakref.proxy(revmap) 527 self.revmap = revmap
528 self._cache = {} 528 self._cache = cache
529 529
530 def get(self, key, default=None): 530 def get(self, key, default=None):
531 if key not in self._cache: 531 if key not in self._cache:
532 result = None 532 result = None
533 for row in self.revmap._query( 533 for row in self.revmap._query(
563 self._dbpath = revmap_path + '.db' 563 self._dbpath = revmap_path + '.db'
564 self._rowcountpath = self._dbpath + '.rowcount' 564 self._rowcountpath = self._dbpath + '.rowcount'
565 self._lastpulledpath = lastpulled_path 565 self._lastpulledpath = lastpulled_path
566 566
567 self._db = None 567 self._db = None
568 self._hashes = None
569 self._sqlitepragmas = sqlitepragmas 568 self._sqlitepragmas = sqlitepragmas
570 self.firstpulled = 0 569 self.firstpulled = 0
571 self._updatefirstlastpulled() 570 self._updatefirstlastpulled()
572 # __iter__ is expensive and thus disabled by default 571 # __iter__ is expensive and thus disabled by default
573 # it should only be enabled for testing 572 # it should only be enabled for testing
574 self._allowiter = False 573 self._allowiter = False
574 # used by self.hashes(), {hash: (rev, branch)}
575 self._hashescache = {}
575 576
576 def hashes(self): 577 def hashes(self):
577 if self._hashes is None: 578 return self.ReverseRevMap(self, self._hashescache)
578 self._hashes = self.ReverseRevMap(self)
579 return self._hashes
580 579
581 def branchedits(self, branch, revnum): 580 def branchedits(self, branch, revnum):
582 return [((r[0], r[1] or None), bytes(r[2])) for r in 581 return [((r[0], r[1] or None), bytes(r[2])) for r in
583 self._query('SELECT rev, branch, hash FROM revmap ' + 582 self._query('SELECT rev, branch, hash FROM revmap ' +
584 'WHERE rev < ? AND branch = ? ' + 583 'WHERE rev < ? AND branch = ? ' +
607 def clear(self): 606 def clear(self):
608 hgutil.unlinkpath(self._filepath, ignoremissing=True) 607 hgutil.unlinkpath(self._filepath, ignoremissing=True)
609 hgutil.unlinkpath(self._dbpath, ignoremissing=True) 608 hgutil.unlinkpath(self._dbpath, ignoremissing=True)
610 hgutil.unlinkpath(self._rowcountpath, ignoremissing=True) 609 hgutil.unlinkpath(self._rowcountpath, ignoremissing=True)
611 self._db = None 610 self._db = None
612 self._hashes = None 611 self._hashescache = {}
613 self._firstpull = None 612 self._firstpull = None
614 self._lastpull = None 613 self._lastpull = None
615 614
616 def batchset(self, items, lastpulled): 615 def batchset(self, items, lastpulled):
617 with self._transaction(): 616 with self._transaction():
644 self._insert([(revnum, branch, binha)]) 643 self._insert([(revnum, branch, binha)])
645 if revnum < self.firstpulled or not self.firstpulled: 644 if revnum < self.firstpulled or not self.firstpulled:
646 self.firstpulled = revnum 645 self.firstpulled = revnum
647 if revnum > self.lastpulled or not self.lastpulled: 646 if revnum > self.lastpulled or not self.lastpulled:
648 self.lastpulled = revnum 647 self.lastpulled = revnum
649 if self._hashes is not None: 648 if self._hashescache:
650 self._hashes._cache[binha] = key 649 self._hashescache[binha] = key
651 650
652 def __delitem__(self, key): 651 def __delitem__(self, key):
653 for row in self._querybykey('DELETE', key): 652 for row in self._querybykey('DELETE', key):
654 if self.rowcount > 0: 653 if self.rowcount > 0:
655 self.rowcount -= 1 654 self.rowcount -= 1
656 return 655 return
657 # For performance reason, self._hashes is not updated 656 # For performance reason, self._hashescache is not updated
658 raise KeyError(key) 657 raise KeyError(key)
659 658
660 @contextlib.contextmanager 659 @contextlib.contextmanager
661 def _transaction(self, mode='IMMEDIATE'): 660 def _transaction(self, mode='IMMEDIATE'):
662 if self._db is None: 661 if self._db is None: