diff mercurial/branching/rev_cache.py @ 51901: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
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