Mercurial > hg
changeset 32752:dc7efa2826e4
context: avoid writing outdated dirstate out (issue5584)
Before this patch, workingctx.status() may cause writing outdated
dirstate out, if:
- .hg/dirstate is changed simultaneously after last loading it,
- there is any file, which should be dirstate.normal()-ed
Typical issue case is:
- the working directory is updated by "hg update"
- .hg/dirstate is updated in background (e.g. fsmonitor)
This patch compares identities of dirstate before and after
acquisition of wlock, and avoids writing outdated dirstate out, if
change of .hg/dirstate is detected.
author | FUJIWARA Katsunori <foozy@lares.dti.ne.jp> |
---|---|
date | Fri, 09 Jun 2017 13:07:49 +0900 |
parents | 627eaab1ad07 |
children | 264b86cf2092 |
files | mercurial/context.py tests/test-dirstate-race.t |
diffstat | 2 files changed, 81 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/context.py Fri Jun 09 13:07:49 2017 +0900 +++ b/mercurial/context.py Fri Jun 09 13:07:49 2017 +0900 @@ -1759,19 +1759,30 @@ # update dirstate for files that are actually clean if fixup: try: + oldid = self._repo.dirstate.identity() + # updating the dirstate is optional # so we don't wait on the lock # wlock can invalidate the dirstate, so cache normal _after_ # taking the lock with self._repo.wlock(False): - normal = self._repo.dirstate.normal - for f in fixup: - normal(f) - # write changes out explicitly, because nesting - # wlock at runtime may prevent 'wlock.release()' - # after this block from doing so for subsequent - # changing files - self._repo.dirstate.write(self._repo.currenttransaction()) + if self._repo.dirstate.identity() == oldid: + normal = self._repo.dirstate.normal + for f in fixup: + normal(f) + # write changes out explicitly, because nesting + # wlock at runtime may prevent 'wlock.release()' + # after this block from doing so for subsequent + # changing files + tr = self._repo.currenttransaction() + self._repo.dirstate.write(tr) + else: + # in this case, writing changes out breaks + # consistency, because .hg/dirstate was + # already changed simultaneously after last + # caching (see also issue5584 for detail) + self._repo.ui.debug('skip updating dirstate: ' + 'identity mismatch\n') except error.LockError: pass return modified, deleted, fixup
--- a/tests/test-dirstate-race.t Fri Jun 09 13:07:49 2017 +0900 +++ b/tests/test-dirstate-race.t Fri Jun 09 13:07:49 2017 +0900 @@ -95,3 +95,65 @@ ! d ! dir1/c ! e + + $ rmdir d e + $ hg update -C -q . + +Test that dirstate changes aren't written out at the end of "hg +status", if .hg/dirstate is already changed simultaneously before +acquisition of wlock in workingctx._checklookup(). + +This avoidance is important to keep consistency of dirstate in race +condition (see issue5584 for detail). + + $ hg parents -q + 1:* (glob) + + $ hg debugrebuilddirstate + $ hg debugdirstate + n 0 -1 unset a + n 0 -1 unset b + n 0 -1 unset d + n 0 -1 unset dir1/c + n 0 -1 unset e + + $ cat > $TESTTMP/dirstaterace.sh <<EOF + > # This script assumes timetable of typical issue5584 case below: + > # + > # 1. "hg status" loads .hg/dirstate + > # 2. "hg status" confirms clean-ness of FILE + > # 3. "hg update -C 0" updates the working directory simultaneously + > # (FILE is removed, and FILE is dropped from .hg/dirstate) + > # 4. "hg status" acquires wlock + > # (.hg/dirstate is re-loaded = no FILE entry in dirstate) + > # 5. "hg status" marks FILE in dirstate as clean + > # (FILE entry is added to in-memory dirstate) + > # 6. "hg status" writes dirstate changes into .hg/dirstate + > # (FILE entry is written into .hg/dirstate) + > # + > # To reproduce similar situation easily and certainly, #2 and #3 + > # are swapped. "hg cat" below ensures #2 on "hg status" side. + > + > hg update -q -C 0 + > hg cat -r 1 b > b + > EOF + +"hg status" below should excludes "e", of which exec flag is set, for +portability of test scenario, because unsure but missing "e" is +treated differently in _checklookup() according to runtime platform. + +- "missing(!)" on POSIX, "pctx[f].cmp(self[f])" raises ENOENT +- "modified(M)" on Windows, "self.flags(f) != pctx.flags(f)" is True + + $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py --debug -X path:e + skip updating dirstate: identity mismatch + M a + ! d + ! dir1/c + + $ hg parents -q + 0:* (glob) + $ hg files + a + $ hg debugdirstate + n * * * a (glob)