diff mercurial/dirstatemap.py @ 50252:a6b8b1ab9116

branching: merge stable into default The clippy god had to be appeased on some aspect.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 02 Mar 2023 19:02:52 +0100
parents 2f60cd6442fd dbe09fb038fc
children 2b1cd454793a bf16ef96defe
line wrap: on
line diff
--- a/mercurial/dirstatemap.py	Thu Mar 02 04:16:47 2023 +0100
+++ b/mercurial/dirstatemap.py	Thu Mar 02 19:02:52 2023 +0100
@@ -10,6 +10,7 @@
     error,
     pathutil,
     policy,
+    testing,
     txnutil,
     util,
 )
@@ -31,6 +32,13 @@
 
 rangemask = 0x7FFFFFFF
 
+WRITE_MODE_AUTO = 0
+WRITE_MODE_FORCE_NEW = 1
+WRITE_MODE_FORCE_APPEND = 2
+
+
+V2_MAX_READ_ATTEMPTS = 5
+
 
 class _dirstatemapcommon:
     """
@@ -54,6 +62,16 @@
         self._parents = None
         self._dirtyparents = False
         self._docket = None
+        write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode")
+        if write_mode == b"auto":
+            self._write_mode = WRITE_MODE_AUTO
+        elif write_mode == b"force-append":
+            self._write_mode = WRITE_MODE_FORCE_APPEND
+        elif write_mode == b"force-new":
+            self._write_mode = WRITE_MODE_FORCE_NEW
+        else:
+            # unknown value, fallback to default
+            self._write_mode = WRITE_MODE_AUTO
 
         # for consistent view between _pl() and _read() invocations
         self._pendingmode = None
@@ -132,11 +150,31 @@
                 raise error.ProgrammingError(
                     b'dirstate only has a docket in v2 format'
                 )
+            self._set_identity()
             self._docket = docketmod.DirstateDocket.parse(
                 self._readdirstatefile(), self._nodeconstants
             )
         return self._docket
 
+    def _read_v2_data(self):
+        data = None
+        attempts = 0
+        while attempts < V2_MAX_READ_ATTEMPTS:
+            attempts += 1
+            try:
+                # TODO: use mmap when possible
+                data = self._opener.read(self.docket.data_filename())
+            except FileNotFoundError:
+                # read race detected between docket and data file
+                # reload the docket and retry
+                self._docket = None
+        if data is None:
+            assert attempts >= V2_MAX_READ_ATTEMPTS
+            msg = b"dirstate read race happened %d times in a row"
+            msg %= attempts
+            raise error.Abort(msg)
+        return self._opener.read(self.docket.data_filename())
+
     def write_v2_no_append(self, tr, st, meta, packed):
         old_docket = self.docket
         new_docket = docketmod.DirstateDocket.with_new_uuid(
@@ -290,14 +328,15 @@
     ### disk interaction
 
     def read(self):
-        # ignore HG_PENDING because identity is used only for writing
-        self._set_identity()
+        testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
+        if self._use_dirstate_v2:
 
-        if self._use_dirstate_v2:
             if not self.docket.uuid:
                 return
-            st = self._opener.read(self.docket.data_filename())
+            testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
+            st = self._read_v2_data()
         else:
+            self._set_identity()
             st = self._readdirstatefile()
 
         if not st:
@@ -556,19 +595,39 @@
             # ignore HG_PENDING because identity is used only for writing
             self._set_identity()
 
+            testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
             if self._use_dirstate_v2:
-                if self.docket.uuid:
-                    # TODO: use mmap when possible
-                    data = self._opener.read(self.docket.data_filename())
+                self.docket  # load the data if needed
+                inode = (
+                    self.identity.stat.st_ino
+                    if self.identity is not None
+                    and self.identity.stat is not None
+                    else None
+                )
+                testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
+                if not self.docket.uuid:
+                    data = b''
+                    self._map = rustmod.DirstateMap.new_empty()
                 else:
-                    data = b''
-                self._map = rustmod.DirstateMap.new_v2(
-                    data, self.docket.data_size, self.docket.tree_metadata
-                )
+                    data = self._read_v2_data()
+                    self._map = rustmod.DirstateMap.new_v2(
+                        data,
+                        self.docket.data_size,
+                        self.docket.tree_metadata,
+                        self.docket.uuid,
+                        inode,
+                    )
                 parents = self.docket.parents
             else:
+                self._set_identity()
+                inode = (
+                    self.identity.stat.st_ino
+                    if self.identity is not None
+                    and self.identity.stat is not None
+                    else None
+                )
                 self._map, parents = rustmod.DirstateMap.new_v1(
-                    self._readdirstatefile()
+                    self._readdirstatefile(), inode
                 )
 
             if parents and not self._dirtyparents:
@@ -638,8 +697,10 @@
                 return
 
             # We can only append to an existing data file if there is one
-            can_append = self.docket.uuid is not None
-            packed, meta, append = self._map.write_v2(can_append)
+            write_mode = self._write_mode
+            if self.docket.uuid is None:
+                write_mode = WRITE_MODE_FORCE_NEW
+            packed, meta, append = self._map.write_v2(write_mode)
             if append:
                 docket = self.docket
                 data_filename = docket.data_filename()