# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@octobus.net>
# Date 2023-02-28 18:01:20
# Node ID a3b1ab5f5deeaf0a10fc2483220a4fab751c87df
# Parent  53f19662269970b8a77aa68755a86a0bfe5a8c00

dirstate: deal with read-race for pure python code

If we cannot read the dirstate data, this is probably because a writing process
wrote it under our feet. So refresh the docket and try again a handful of time.

diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py
--- a/mercurial/dirstatemap.py
+++ b/mercurial/dirstatemap.py
@@ -37,6 +37,9 @@ WRITE_MODE_FORCE_NEW = 1
 WRITE_MODE_FORCE_APPEND = 2
 
 
+V2_MAX_READ_ATTEMPTS = 5
+
+
 class _dirstatemapcommon:
     """
     Methods that are identical for both implementations of the dirstatemap
@@ -125,6 +128,21 @@ class _dirstatemapcommon:
         return self._docket
 
     def _read_v2_data(self):
+        data = None
+        attempts = 0
+        while attempts < V2_MAX_READ_ATTEMPTS:
+            attempts += 1
+            try:
+                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):
diff --git a/tests/test-dirstate-read-race.t b/tests/test-dirstate-read-race.t
--- a/tests/test-dirstate-read-race.t
+++ b/tests/test-dirstate-read-race.t
@@ -217,8 +217,12 @@ The status process should return a consi
 #endif
 #else
   $ cat $TESTTMP/status-race-lock.out
+  A dir/n
+  A dir/o
+  R dir/nested/m
+  ? p
+  ? q
   $ cat $TESTTMP/status-race-lock.log
-  abort: $ENOENT$: '$TESTTMP/race-with-add/.hg/dirstate.* (glob)
 #endif
 #endif
 #endif
@@ -318,8 +322,12 @@ The status process should return a consi
 #endif
 #else
   $ cat $TESTTMP/status-race-lock.out
+  M dir/o
+  ? dir/n
+  ? p
+  ? q
   $ cat $TESTTMP/status-race-lock.log
-  abort: $ENOENT$: '$TESTTMP/race-with-commit/.hg/dirstate.* (glob)
+  warning: ignoring unknown working parent 02a67a77ee9b!
 #endif
 #endif
 #endif
@@ -452,8 +460,11 @@ The status process should return a consi
 #endif
 #else
   $ cat $TESTTMP/status-race-lock.out
+  A dir/o
+  ? dir/n
+  ? p
+  ? q
   $ cat $TESTTMP/status-race-lock.log
-  abort: $ENOENT$: '$TESTTMP/race-with-update/.hg/dirstate.* (glob)
 #endif
 #endif
 #endif
@@ -542,8 +553,12 @@ The status process should return a consi
 #endif
 #else
   $ cat $TESTTMP/status-race-lock.out
+  A dir/o
+  R dir/nested/m
+  ? dir/n
+  ? p
+  ? q
   $ cat $TESTTMP/status-race-lock.log
-  abort: $ENOENT$: '$TESTTMP/race-with-status/.hg/dirstate.* (glob)
 #endif
 #endif
 #endif