comparison mercurial/context.py @ 48438:322525db4c98

status: use filesystem time boundary to invalidate racy mtime We record the filesystem time at the start of the status walk and use that as a boundary to detect files that might be modified during (or right after) the status run without the mtime allowing that edition to be detected. We currently do this at a second precision. In a later patch, we will use nanosecond precision when available. To cope with "broken" time on the file system where file could be in the future, we also keep mtime for file over one day in the future. See inline comment for details. Large file tests get a bit more confused as we reduce the odds for race condition. As a "side effect", the win32text extension is happy again. Differential Revision: https://phab.mercurial-scm.org/D11794
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 18 Nov 2021 13:12:40 +0100
parents 41f40f35278a
children 9ae0353c9f5d
comparison
equal deleted inserted replaced
48437:03644a929d6e 48438:322525db4c98
1794 ) 1794 )
1795 continue 1795 continue
1796 sane.append(f) 1796 sane.append(f)
1797 return sane 1797 return sane
1798 1798
1799 def _checklookup(self, files): 1799 def _checklookup(self, files, mtime_boundary):
1800 # check for any possibly clean files 1800 # check for any possibly clean files
1801 if not files: 1801 if not files:
1802 return [], [], [] 1802 return [], [], [], []
1803 1803
1804 modified = [] 1804 modified = []
1805 deleted = [] 1805 deleted = []
1806 clean = []
1806 fixup = [] 1807 fixup = []
1807 pctx = self._parents[0] 1808 pctx = self._parents[0]
1808 # do a full compare of any files that might have changed 1809 # do a full compare of any files that might have changed
1809 for f in sorted(files): 1810 for f in sorted(files):
1810 try: 1811 try:
1814 f not in pctx 1815 f not in pctx
1815 or self.flags(f) != pctx.flags(f) 1816 or self.flags(f) != pctx.flags(f)
1816 or pctx[f].cmp(self[f]) 1817 or pctx[f].cmp(self[f])
1817 ): 1818 ):
1818 modified.append(f) 1819 modified.append(f)
1820 elif mtime_boundary is None:
1821 clean.append(f)
1819 else: 1822 else:
1820 # XXX note that we have a race windows here since we gather
1821 # the stats after we compared so the file might have
1822 # changed.
1823 #
1824 # However this have always been the case and the
1825 # refactoring moving the code here is improving the
1826 # situation by narrowing the race and moving the two steps
1827 # (comparison + stat) in the same location.
1828 #
1829 # Making this code "correct" is now possible.
1830 s = self[f].lstat() 1823 s = self[f].lstat()
1831 mode = s.st_mode 1824 mode = s.st_mode
1832 size = s.st_size 1825 size = s.st_size
1833 mtime = timestamp.mtime_of(s) 1826 file_mtime = timestamp.mtime_of(s)
1834 fixup.append((f, (mode, size, mtime))) 1827 cache_info = (mode, size, file_mtime)
1828
1829 file_second = file_mtime[0]
1830 boundary_second = mtime_boundary[0]
1831 # If the mtime of the ambiguous file is younger (or equal)
1832 # to the starting point of the `status` walk, we cannot
1833 # garantee that another, racy, write will not happen right
1834 # after with the same mtime and we cannot cache the
1835 # information.
1836 #
1837 # However is the mtime is far away in the future, this is
1838 # likely some mismatch between the current clock and
1839 # previous file system operation. So mtime more than one days
1840 # in the future are considered fine.
1841 if (
1842 boundary_second
1843 <= file_second
1844 < (3600 * 24 + boundary_second)
1845 ):
1846 clean.append(f)
1847 else:
1848 fixup.append((f, cache_info))
1835 except (IOError, OSError): 1849 except (IOError, OSError):
1836 # A file become inaccessible in between? Mark it as deleted, 1850 # A file become inaccessible in between? Mark it as deleted,
1837 # matching dirstate behavior (issue5584). 1851 # matching dirstate behavior (issue5584).
1838 # The dirstate has more complex behavior around whether a 1852 # The dirstate has more complex behavior around whether a
1839 # missing file matches a directory, etc, but we don't need to 1853 # missing file matches a directory, etc, but we don't need to
1840 # bother with that: if f has made it to this point, we're sure 1854 # bother with that: if f has made it to this point, we're sure
1841 # it's in the dirstate. 1855 # it's in the dirstate.
1842 deleted.append(f) 1856 deleted.append(f)
1843 1857
1844 return modified, deleted, fixup 1858 return modified, deleted, clean, fixup
1845 1859
1846 def _poststatusfixup(self, status, fixup): 1860 def _poststatusfixup(self, status, fixup):
1847 """update dirstate for files that are actually clean""" 1861 """update dirstate for files that are actually clean"""
1848 poststatus = self._repo.postdsstatus() 1862 poststatus = self._repo.postdsstatus()
1849 if fixup or poststatus or self._repo.dirstate._dirty: 1863 if fixup or poststatus or self._repo.dirstate._dirty:
1893 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False): 1907 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1894 '''Gets the status from the dirstate -- internal use only.''' 1908 '''Gets the status from the dirstate -- internal use only.'''
1895 subrepos = [] 1909 subrepos = []
1896 if b'.hgsub' in self: 1910 if b'.hgsub' in self:
1897 subrepos = sorted(self.substate) 1911 subrepos = sorted(self.substate)
1898 cmp, s = self._repo.dirstate.status( 1912 cmp, s, mtime_boundary = self._repo.dirstate.status(
1899 match, subrepos, ignored=ignored, clean=clean, unknown=unknown 1913 match, subrepos, ignored=ignored, clean=clean, unknown=unknown
1900 ) 1914 )
1901 1915
1902 # check for any possibly clean files 1916 # check for any possibly clean files
1903 fixup = [] 1917 fixup = []
1904 if cmp: 1918 if cmp:
1905 modified2, deleted2, fixup = self._checklookup(cmp) 1919 modified2, deleted2, clean_set, fixup = self._checklookup(
1920 cmp, mtime_boundary
1921 )
1906 s.modified.extend(modified2) 1922 s.modified.extend(modified2)
1907 s.deleted.extend(deleted2) 1923 s.deleted.extend(deleted2)
1908 1924
1925 if clean_set and clean:
1926 s.clean.extend(clean_set)
1909 if fixup and clean: 1927 if fixup and clean:
1910 s.clean.extend((f for f, _ in fixup)) 1928 s.clean.extend((f for f, _ in fixup))
1911 1929
1912 self._poststatusfixup(s, fixup) 1930 self._poststatusfixup(s, fixup)
1913 1931