changeset 51905:c564be351754

rev-branch-cache: stop truncating cache file Truncating the file prevent the safe use of mmap. So instead of overwrite the existing data. If more than 20% of the file is to be overwritten, we rewrite the whole file instead. Such whole rewrite is done by replacing the old one with a new one, so mmap of the old file would be affected. This prepare a more aggressive use of mmap in later patches.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 24 Sep 2024 00:16:23 +0200
parents 7032da075572
children 0f26ee69cf36
files mercurial/branching/rev_cache.py tests/test-acl.t tests/test-branches.t tests/test-rebase-conflicts.t tests/test-strip.t
diffstat 5 files changed, 54 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/branching/rev_cache.py	Tue Sep 24 00:16:04 2024 +0200
+++ b/mercurial/branching/rev_cache.py	Tue Sep 24 00:16:23 2024 +0200
@@ -4,6 +4,7 @@
 # GNU General Public License version 2 or any later version.
 from __future__ import annotations
 
+import os
 import struct
 
 from ..node import (
@@ -39,6 +40,10 @@
 _rbccloseflag = 0x80000000
 
 
+# with atomic replacement.
+REWRITE_RATIO = 0.2
+
+
 class rbcrevs:
     """a byte string consisting of an immutable prefix followed by a mutable suffix"""
 
@@ -345,19 +350,44 @@
     def _writerevs(self, repo, start):
         """write the new revs to revbranchcache"""
         revs = min(len(repo.changelog), len(self._rbcrevs) // _rbcrecsize)
+
+        end = revs * _rbcrecsize
         if self._force_overwrite:
             start = 0
-        with repo.cachevfs.open(_rbcrevs, b'ab') as f:
+
+        with repo.cachevfs.open(_rbcrevs, b'a+b') as f:
+            pass  # this make sure the file exist…
+        with repo.cachevfs.open(_rbcrevs, b'r+b') as f:
+            f.seek(0, os.SEEK_END)
             current_size = f.tell()
             if current_size < start:
                 start = 0
             if current_size != start:
-                msg = b"truncating cache/%s to %d\n"
-                msg %= (_rbcrevs, start)
-                repo.ui.debug(msg)
+                threshold = current_size * REWRITE_RATIO
+                if (max(end, current_size) - start) < threshold:
+                    # end affected, let overwrite the bad value
+                    dbg = b"overwriting %d bytes from %d in cache/%s"
+                    dbg %= (current_size - start, start, _rbcrevs)
+                    if end < current_size:
+                        extra = b" leaving (%d trailing bytes)"
+                        extra %= current_size - end
+                        dbg += extra
+                    dbg += b'\n'
+                    repo.ui.debug(dbg)
+                else:
+                    start = 0
+                    dbg = b"resetting content of cache/%s\n" % _rbcrevs
+                    repo.ui.debug(dbg)
+            if start > 0:
                 f.seek(start)
-                f.truncate()
-            end = revs * _rbcrecsize
-            f.write(self._rbcrevs.slice(start, end))
+                f.write(self._rbcrevs.slice(start, end))
+            else:
+                f.close()
+                with repo.cachevfs.open(
+                    _rbcrevs,
+                    b'wb',
+                    atomictemp=True,
+                ) as rev_file:
+                    rev_file.write(self._rbcrevs.slice(start, end))
         self._rbcrevslen = revs
         self._force_overwrite = False
--- a/tests/test-acl.t	Tue Sep 24 00:16:04 2024 +0200
+++ b/tests/test-acl.t	Tue Sep 24 00:16:23 2024 +0200
@@ -202,7 +202,7 @@
   bundle2-input-part: "phase-heads" supported
   bundle2-input-part: total payload size * (glob)
   bundle2-input-bundle: 5 parts total
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
   updating the branch cache
   added 3 changesets with 3 changes to 3 files
   bundle2-output-bundle: "HG20", 1 parts total
@@ -280,7 +280,7 @@
   bundle2-input-part: "phase-heads" supported
   bundle2-input-part: total payload size * (glob)
   bundle2-input-bundle: 5 parts total
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
   updating the branch cache
   added 3 changesets with 3 changes to 3 files
   bundle2-output-bundle: "HG20", 1 parts total
@@ -355,7 +355,7 @@
   bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
   abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   no rollback information available
   0:6675d58eff77
@@ -879,7 +879,7 @@
   bundle2-input-bundle: 7 parts total
   transaction abort!
   rollback completed
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
   abort: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
   no rollback information available
   0:6675d58eff77
@@ -1048,7 +1048,7 @@
   bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
   abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
   no rollback information available
   0:6675d58eff77
@@ -1380,7 +1380,7 @@
   bundle2-input-part: "phase-heads" supported
   bundle2-input-part: total payload size * (glob)
   bundle2-input-bundle: 5 parts total
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
   updating the branch cache
   added 3 changesets with 3 changes to 3 files
   bundle2-output-bundle: "HG20", 1 parts total
@@ -1464,7 +1464,7 @@
   bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   no rollback information available
   0:6675d58eff77
@@ -1632,7 +1632,7 @@
   bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   no rollback information available
   0:6675d58eff77
--- a/tests/test-branches.t	Tue Sep 24 00:16:04 2024 +0200
+++ b/tests/test-branches.t	Tue Sep 24 00:16:23 2024 +0200
@@ -835,9 +835,9 @@
   $ echo >> .hg/cache/rbc-revs-v1
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
-  truncating cache/rbc-revs-v1 to 160
+  overwriting 2 bytes from 160 in cache/rbc-revs-v1 leaving (2 trailing bytes)
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=160
+  .hg/cache/rbc-revs-v1: size=162
 
 recovery from invalid cache file with partial last record
   $ mv .hg/cache/rbc-revs-v1 .
@@ -846,7 +846,7 @@
   .hg/cache/rbc-revs-v1: size=119
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
-  truncating cache/rbc-revs-v1 to 112
+  resetting content of cache/rbc-revs-v1
   $ f --size .hg/cache/rbc-revs*
   .hg/cache/rbc-revs-v1: size=160
 
@@ -869,10 +869,10 @@
   $ hg log -r 'branch(.)' -T '{rev} ' --debug
   history modification detected - truncating revision branch cache to revision * (glob)
   history modification detected - truncating revision branch cache to revision 1
-  3 4 8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 8
+  3 4 8 9 10 11 12 13 resetting content of cache/rbc-revs-v1
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
-  truncating cache/rbc-revs-v1 to 104
+  resetting content of cache/rbc-revs-v1
   $ f --size --hexdump --bytes=16 .hg/cache/rbc-revs*
   .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
@@ -893,7 +893,7 @@
   .hg/cache/rbc-names-v1: size=111
   $ grep "i-will-regret-this" .hg/cache/rbc-names-* > /dev/null
   $ f --size .hg/cache/rbc-revs-*
-  .hg/cache/rbc-revs-v1: size=160
+  .hg/cache/rbc-revs-v1: size=168
 
 cache is updated/truncated when stripping - it is thus very hard to get in a
 situation where the cache is out of sync and the hash check detects it
--- a/tests/test-rebase-conflicts.t	Tue Sep 24 00:16:04 2024 +0200
+++ b/tests/test-rebase-conflicts.t	Tue Sep 24 00:16:23 2024 +0200
@@ -319,14 +319,14 @@
   bundle2-input-part: "phase-heads" supported
   bundle2-input-part: total payload size 24
   bundle2-input-bundle: 3 parts total
-  truncating cache/rbc-revs-v1 to 0
+  resetting content of cache/rbc-revs-v1
   added 2 changesets with 2 changes to 1 files
   updating the branch cache
   invalid branch cache (served): tip differs
   history modification detected - truncating revision branch cache to revision 1
   invalid branch cache (served.hidden): tip differs
   rebase completed
-  truncating cache/rbc-revs-v1 to 8
+  resetting content of cache/rbc-revs-v1
 
 Test minimization of merge conflicts
   $ hg up -q null
--- a/tests/test-strip.t	Tue Sep 24 00:16:04 2024 +0200
+++ b/tests/test-strip.t	Tue Sep 24 00:16:23 2024 +0200
@@ -913,7 +913,7 @@
   saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/6625a5168474-345bb43d-backup.hg
   updating the branch cache
   invalid branch cache (served): tip differs
-  truncating cache/rbc-revs-v1 to 0
+  resetting content of cache/rbc-revs-v1
   $ hg log -G
   o  changeset:   2:5c51d8d6557d
   |  tag:         tip