hgext3rd/evolve/obscache.py
changeset 2345 406c1a57b4ee
parent 2344 827d0f0a483f
child 2346 34c6382dbb82
equal deleted inserted replaced
2344:827d0f0a483f 2345:406c1a57b4ee
    97 
    97 
    98     'start-obs-idx': index of the first obs-markers not in the cache. None is
    98     'start-obs-idx': index of the first obs-markers not in the cache. None is
    99                      up to date.
    99                      up to date.
   100     """
   100     """
   101 
   101 
   102     # XXX ideally, this function would return a bounded amount of changeset and
   102     # XXX we need to ensure we use the same changelog and obsstore through the processing
   103     # obsmarkers and the associated new cache key. Otherwise we are exposed to
   103 
   104     # a race condition between the time the cache is updated and the new cache
   104     reset = False
   105     # key is computed. (however, we do not want to compute the full new cache
       
   106     # key in all case because we want to skip reading the obsstore content. We
       
   107     # could have a smarter implementation here.
       
   108     #
       
   109     # In pratice the cache is only updated after each transaction within a
       
   110     # lock. So we should be fine. We could enforce this with a new repository
       
   111     # requirement (or fix the race, that is not too hard).
       
   112 
   105 
   113     status = _checkkey(repo, key)
   106     status = _checkkey(repo, key)
   114     if status is None:
   107     if status is None:
   115         return (False, 0, 0)
   108         reset = True
       
   109         key = emptykey
       
   110         obssize, obskey = repo.obsstore.cachekey()
       
   111         tiprev = len(repo.changelog) - 1
       
   112     else:
       
   113         tiprev, obssize, obskey = status
   116 
   114 
   117     keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key
   115     keytiprev, keytipnode, keyobslength, keyobssize, keyobskey = key
   118     tiprev, obssize, __ = status
   116 
       
   117     if not reset and keytiprev == tiprev and keyobssize == obssize:
       
   118         return None # nothing to upgrade
   119 
   119 
   120     ### cache is valid, is there anything to update
   120     ### cache is valid, is there anything to update
   121 
   121 
   122     # any new changesets ?
   122     # any new changesets ?
   123     startrev = None
   123     revs = ()
   124     if keytiprev < tiprev:
   124     if keytiprev < tiprev:
   125         startrev = keytiprev + 1
   125         revs = list(repo.changelog.revs(start=keytiprev + 1, stop=tiprev))
   126 
   126 
   127     # any new markers
   127     # any new markers
   128     startidx = None
   128     markers = ()
   129     if keyobssize < obssize:
   129     if keyobssize < obssize:
   130         startidx = keyobslength
   130         # XXX Three are a small race change here. Since the obsstore might have
   131 
   131         # move forward between the time we computed the cache key and we access
   132     return True, startrev, startidx
   132         # the data. To fix this we need so "up to" argument when fetching the
       
   133         # markers here. Otherwise we might return more markers than covered by
       
   134         # the cache key.
       
   135         #
       
   136         # In pratice the cache is only updated after each transaction within a
       
   137         # lock. So we should be fine. We could enforce this with a new repository
       
   138         # requirement (or fix the race, that is not too hard).
       
   139         markers = markersfrom(repo.obsstore, keyobssize, keyobslength)
       
   140 
       
   141     return reset, revs, markers, (obssize, obskey)
   133 
   142 
   134 def _checkkey(repo, key):
   143 def _checkkey(repo, key):
   135     """internal function"""
   144     """internal function"""
   136     if key is None:
   145     if key is None:
   137         return None
   146         return None
   165         raise error.Abort(_('parsing obsolete marker: unknown version %r')
   174         raise error.Abort(_('parsing obsolete marker: unknown version %r')
   166                           % diskversion)
   175                           % diskversion)
   167     return diskversion, obsolete.formats[diskversion][0](data, off)
   176     return diskversion, obsolete.formats[diskversion][0](data, off)
   168 
   177 
   169 def markersfrom(obsstore, byteoffset, firstmarker):
   178 def markersfrom(obsstore, byteoffset, firstmarker):
   170     if '_all' in vars(obsstore):
   179     if not firstmarker:
       
   180         return list(obsstore)
       
   181     elif '_all' in vars(obsstore):
   171         # if the data are in memory, just use that
   182         # if the data are in memory, just use that
   172         return obsstore._all[firstmarker:]
   183         return obsstore._all[firstmarker:]
   173     else:
   184     else:
   174         obsdata = obsstore.svfs.tryread('obsstore')
   185         obsdata = obsstore.svfs.tryread('obsstore')
   175         return _readmarkers(obsdata, byteoffset)[1]
   186         return _readmarkers(obsdata, byteoffset)[1]
   251         """Iteratively update the cache with new repository data"""
   262         """Iteratively update the cache with new repository data"""
   252         # If we do not have any data, try loading from disk
   263         # If we do not have any data, try loading from disk
   253         if self._cachekey is None:
   264         if self._cachekey is None:
   254             self.load(repo)
   265             self.load(repo)
   255 
   266 
   256         valid, startrev, startidx = upgradeneeded(repo, self._cachekey)
   267         assert repo.filtername is None
   257         if not valid or self._cachekey is None:
   268         cl = repo.changelog
       
   269 
       
   270         upgrade = upgradeneeded(repo, self._cachekey)
       
   271         if upgrade is None:
       
   272             return
       
   273 
       
   274         reset, revs, obsmarkers, obskeypair = upgrade
       
   275         if reset or self._cachekey is None:
   258             self.clear(reset=True)
   276             self.clear(reset=True)
   259 
   277 
   260         if startrev is None and startidx is None:
   278         if revs:
   261             return
   279             self._updaterevs(repo, revs)
   262 
   280             assert len(self._data) == len(cl), (len(self._data), len(cl))
   263         # checks we never run 'update' without a lock
   281         if obsmarkers:
   264         #
   282             self._updatemarkers(repo, obsmarkers)
   265         # There are a potential race condition otherwise, since the repo
       
   266         # "might" have changed side the cache update above. However, this code
       
   267         # will only be running in a lock so we ignore the issue for now.
       
   268         #
       
   269         # Lift this limitation, 'upgradeneeded' should return a bounded amount
       
   270         # of changeset and markers to read with their associated cachekey. see
       
   271         # 'upgradeneeded' for detail.
       
   272         assert repo._currentlock(repo._lockref) is not None
       
   273 
       
   274         # process the new changesets
       
   275         cl = repo.changelog
       
   276         if startrev is not None:
       
   277             self._updaterevs(repo, cl.revs(startrev))
       
   278         assert len(self._data) == len(cl), (len(self._data), len(cl))
       
   279 
       
   280         # process the new obsmarkers
       
   281         if startidx is not None:
       
   282             if startidx == 0: # all markers
       
   283                 markers = repo.obsstore._all
       
   284             else:
       
   285                 markers = markersfrom(repo.obsstore, self._cachekey[3], startidx)
       
   286             self._updatemarkers(repo, markers)
       
   287 
   283 
   288         # update the key from the new data
   284         # update the key from the new data
   289         key = list(self._cachekey)
   285         key = list(self._cachekey)
   290         if startrev is not None:
   286         if revs:
   291             key[0] = len(cl) - 1
   287             key[0] = len(cl) - 1
   292             key[1] = cl.node(key[0])
   288             key[1] = cl.node(key[0])
   293         if startidx is not None:
   289         if obsmarkers:
   294             key[2] += len(markers)
   290             key[2] += len(obsmarkers)
   295             # XXX still a small race here if repo is not locked
   291             key[3], key[4] = obskeypair
   296             key[3], key[4] = repo.obsstore.cachekey()
       
   297         self._cachekey = tuple(key)
   292         self._cachekey = tuple(key)
   298 
   293 
   299     def _updaterevs(self, repo, revs):
   294     def _updaterevs(self, repo, revs):
   300         """update the cache with new revisions
   295         """update the cache with new revisions
   301 
   296