branchcache: explicitly track inheritence "state"
authorPierre-Yves David <pierre-yves.david@octobus.net>
Sun, 10 Mar 2024 05:10:00 +0100
changeset 51493 82c1a388e86a
parent 51492 2e8a88e5809f
child 51494 54f0dd798346
branchcache: explicitly track inheritence "state" We move from a binary "dirty" flag to a three value "state": "clean", "inherited", "dirty". The "inherited" means that the branch cache is not only "clean", but it is a duplicate of its parent filter. If a branch cache is "inherited", we can non only skip writing its value on disk, but it is a good idea to delete any stale value on disk, as those will just waste time (and possibly induce bug) in the future. We only do this in the update related to transaction or explicit cache update (e.g `hg debugupdatecache`). Deleting the file when we simply detected a stall cache during a read only operation seems more dangerous. We rename `copy` to `inherit_for` to clarify we associate a stronger semantic to the operation.
contrib/perf.py
mercurial/branchmap.py
tests/test-acl.t
tests/test-blackbox.t
tests/test-strip-branch-cache.t
--- a/contrib/perf.py	Sun Mar 10 04:53:17 2024 +0100
+++ b/contrib/perf.py	Sun Mar 10 05:10:00 2024 +0100
@@ -4303,8 +4303,16 @@
         baserepo = repo.filtered(b'__perf_branchmap_update_base')
         targetrepo = repo.filtered(b'__perf_branchmap_update_target')
 
+        bcache = repo.branchmap()
+        copy_method = 'copy'
+
         copy_base_kwargs = copy_base_kwargs = {}
-        if 'repo' in getargspec(repo.branchmap().copy).args:
+        if hasattr(bcache, 'copy'):
+            if 'repo' in getargspec(bcache.copy).args:
+                copy_base_kwargs = {"repo": baserepo}
+                copy_target_kwargs = {"repo": targetrepo}
+        else:
+            copy_method = 'inherit_for'
             copy_base_kwargs = {"repo": baserepo}
             copy_target_kwargs = {"repo": targetrepo}
 
@@ -4316,7 +4324,7 @@
             if candidatebm.validfor(baserepo):
                 filtered = repoview.filterrevs(repo, candidatefilter)
                 missing = [r for r in allbaserevs if r in filtered]
-                base = candidatebm.copy(**copy_base_kwargs)
+                base = getattr(candidatebm, copy_method)(**copy_base_kwargs)
                 base.update(baserepo, missing)
                 break
             candidatefilter = subsettable.get(candidatefilter)
@@ -4326,7 +4334,7 @@
             base.update(baserepo, allbaserevs)
 
         def setup():
-            x[0] = base.copy(**copy_target_kwargs)
+            x[0] = getattr(base, copy_method)(**copy_target_kwargs)
             if clearcaches:
                 unfi._revbranchcache = None
                 clearchangelog(repo)
--- a/mercurial/branchmap.py	Sun Mar 10 04:53:17 2024 +0100
+++ b/mercurial/branchmap.py	Sun Mar 10 05:10:00 2024 +0100
@@ -85,8 +85,7 @@
             bcache._filtername,
             repo.filtername,
         )
-        if bcache._dirty:
-            bcache.write(repo)
+        bcache.sync_disk(repo)
 
     def updatecache(self, repo):
         """Update the cache for the given filtered view on a repository"""
@@ -109,7 +108,7 @@
             subsetname = subsettable.get(filtername)
             if subsetname is not None:
                 subset = repo.filtered(subsetname)
-                bcache = self[subset].copy(repo)
+                bcache = self[subset].inherit_for(repo)
                 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
                 revs.extend(r for r in extrarevs if r <= bcache.tiprev)
             else:
@@ -160,7 +159,7 @@
                 if cache.validfor(rview):
                     cache._filtername = candidate
                     self._per_filter[candidate] = cache
-                    cache._dirty = True
+                    cache._state = STATE_DIRTY
                     cache.write(rview)
                     return
 
@@ -173,12 +172,11 @@
             cache = self._per_filter.get(filtername)
             if cache is None:
                 continue
-            if cache._dirty:
-                if filtername is None:
-                    repo = unfi
-                else:
-                    repo = unfi.filtered(filtername)
-                cache.write(repo)
+            if filtername is None:
+                repo = unfi
+            else:
+                repo = unfi.filtered(filtername)
+            cache.sync_disk(repo)
 
 
 def _unknownnode(node):
@@ -414,6 +412,11 @@
         return max_rev
 
 
+STATE_CLEAN = 1
+STATE_INHERITED = 2
+STATE_DIRTY = 3
+
+
 class branchcache(_BaseBranchCache):
     """Branchmap info for a local repo or repoview"""
 
@@ -431,6 +434,7 @@
         closednodes: Optional[Set[bytes]] = None,
         hasnode: Optional[Callable[[bytes], bool]] = None,
         verify_node: bool = False,
+        inherited: bool = False,
     ) -> None:
         """hasnode is a function which can be used to verify whether changelog
         has a given node or not. If it's not provided, we assume that every node
@@ -442,7 +446,9 @@
             self.tipnode = tipnode
         self.tiprev = tiprev
         self.filteredhash = filteredhash
-        self._dirty = False
+        self._state = STATE_CLEAN
+        if inherited:
+            self._state = STATE_INHERITED
 
         super().__init__(repo=repo, entries=entries, closed_nodes=closednodes)
         # closednodes is a set of nodes that close their branch. If the branch
@@ -555,7 +561,7 @@
             filename = b'%s-%s' % (filename, repo.filtername)
         return filename
 
-    def copy(self, repo):
+    def inherit_for(self, repo):
         """return a deep copy of the branchcache object"""
         assert repo.filtername != self._filtername
         other = type(self)(
@@ -569,16 +575,33 @@
             filteredhash=self.filteredhash,
             closednodes=set(self._closednodes),
             verify_node=self._verify_node,
+            inherited=True,
         )
         # also copy information about the current verification state
         other._verifiedbranches = set(self._verifiedbranches)
         return other
 
+    def sync_disk(self, repo):
+        """synchronise the on disk file with the cache state
+
+        If new value specific to this filter level need to be written, the file
+        will be updated, if the state of the branchcache is inherited from a
+        subset, any stalled on disk file will be deleted.
+
+        That method does nothing if there is nothing to do.
+        """
+        if self._state == STATE_DIRTY:
+            self.write(repo)
+        elif self._state == STATE_INHERITED:
+            filename = self._filename(repo)
+            repo.cachevfs.tryunlink(filename)
+
     def write(self, repo):
         assert self._filtername == repo.filtername, (
             self._filtername,
             repo.filtername,
         )
+        assert self._state == STATE_DIRTY, self._state
         tr = repo.currenttransaction()
         if not getattr(tr, 'finalized', True):
             # Avoid premature writing.
@@ -597,7 +620,7 @@
                 len(self._entries),
                 nodecount,
             )
-            self._dirty = False
+            self._state = STATE_CLEAN
         except (IOError, OSError, error.Abort) as inst:
             # Abort may be raised by read only opener, so log and continue
             repo.ui.debug(
@@ -707,7 +730,7 @@
         self.filteredhash = scmutil.filteredhash(
             repo, self.tiprev, needobsolete=True
         )
-        self._dirty = True
+        self._state = STATE_DIRTY
         self.write(repo)
 
 
--- a/tests/test-acl.t	Sun Mar 10 04:53:17 2024 +0100
+++ b/tests/test-acl.t	Sun Mar 10 05:10:00 2024 +0100
@@ -167,7 +167,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -187,7 +186,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -237,7 +235,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -257,7 +254,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -317,7 +313,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -337,7 +332,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -388,7 +382,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -408,7 +401,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -463,7 +455,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -483,7 +474,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -535,7 +525,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -555,7 +544,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -612,7 +600,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -632,7 +619,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -686,7 +672,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -706,7 +691,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -761,7 +745,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   1 changesets found
   list of changesets:
@@ -783,7 +766,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -849,7 +831,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   1 changesets found
   list of changesets:
@@ -871,7 +852,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -938,7 +918,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -958,7 +937,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -1024,7 +1002,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -1044,7 +1021,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -1108,7 +1084,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -1128,7 +1103,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -1186,7 +1160,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -1206,7 +1179,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -1275,7 +1247,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -1295,7 +1266,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -1365,7 +1335,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -1385,7 +1354,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -1452,7 +1420,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -1472,7 +1439,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -1535,7 +1501,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -1555,7 +1520,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
@@ -1622,7 +1586,6 @@
   listing keys for "phases"
   checking for updated bookmarks
   listing keys for "bookmarks"
-  invalid branch cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -1642,7 +1605,6 @@
   bundle2-input-part: total payload size * (glob)
   bundle2-input-part: "check:updated-heads" supported
   bundle2-input-part: total payload size * (glob)
-  invalid branch cache (served): tip differs
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
   add changeset ef1ea85a6374
--- a/tests/test-blackbox.t	Sun Mar 10 04:53:17 2024 +0100
+++ b/tests/test-blackbox.t	Sun Mar 10 05:10:00 2024 +0100
@@ -188,13 +188,11 @@
   $ hg strip tip
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
-  $ hg blackbox -l 6
+  $ hg blackbox -l 4
   1970-01-01 00:00:00.000 bob @73f6ee326b27d820b0472f1a825e3a50f3dc489b (5000)> strip tip
   1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/73f6ee326b27-7612e004-backup.hg
-  1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated branch cache (base) in * seconds (glob)
-  1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote branch cache (base) with 1 labels and 2 nodes
   1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> strip tip exited 0 after * seconds (glob)
-  1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
+  1970-01-01 00:00:00.000 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 4
 
 extension and python hooks - use the eol extension for a pythonhook
 
--- a/tests/test-strip-branch-cache.t	Sun Mar 10 04:53:17 2024 +0100
+++ b/tests/test-strip-branch-cache.t	Sun Mar 10 05:10:00 2024 +0100
@@ -43,11 +43,9 @@
 
   $ ls -1 .hg/cache/branch?*
   .hg/cache/branch2-base
-  .hg/cache/branch2-served
-  $ cat .hg/cache/branch?-served
-  222ae9789a75703f9836e44de7db179cbfd420ee 2
-  a3498d6e39376d2456425dd8c692367bdbf00fa2 o default
-  222ae9789a75703f9836e44de7db179cbfd420ee o default
+  $ cat .hg/cache/branch?-base
+  7ab0a3bd758a58b9f79557ce708533e627776cce 0
+  7ab0a3bd758a58b9f79557ce708533e627776cce o default
 
 We do a new commit and we get a new valid branchmap for the served version