Mercurial > public > mercurial-scm > hg-stable
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 |