comparison hgext/fsmonitor/state.py @ 32816:1b25c648d5b7

fsmonitor: don't write out state if identity has changed (issue5581) Inspired by the dirstate fix in dc7efa2826e4, this should fix any race conditions with the fsmonitor state changing from underneath. Since we now grab the wlock for any non-invalidate writes, the only situation this appears to happen in is with a concurrent invalidation. Test that.
author Siddharth Agarwal <sid0@fb.com>
date Mon, 12 Jun 2017 15:34:31 -0700
parents 15c998528c36
children 718f7acd6d5e
comparison
equal deleted inserted replaced
32815:15e85dded933 32816:1b25c648d5b7
11 import os 11 import os
12 import socket 12 import socket
13 import struct 13 import struct
14 14
15 from mercurial.i18n import _ 15 from mercurial.i18n import _
16 from mercurial import pathutil 16 from mercurial import (
17 pathutil,
18 util,
19 )
17 20
18 _version = 4 21 _version = 4
19 _versionformat = ">I" 22 _versionformat = ">I"
20 23
21 class state(object): 24 class state(object):
22 def __init__(self, repo): 25 def __init__(self, repo):
23 self._vfs = repo.vfs 26 self._vfs = repo.vfs
24 self._ui = repo.ui 27 self._ui = repo.ui
25 self._rootdir = pathutil.normasprefix(repo.root) 28 self._rootdir = pathutil.normasprefix(repo.root)
26 self._lastclock = None 29 self._lastclock = None
30 self._identity = util.filestat(None)
27 31
28 self.mode = self._ui.config('fsmonitor', 'mode', default='on') 32 self.mode = self._ui.config('fsmonitor', 'mode', default='on')
29 self.walk_on_invalidate = self._ui.configbool( 33 self.walk_on_invalidate = self._ui.configbool(
30 'fsmonitor', 'walk_on_invalidate', False) 34 'fsmonitor', 'walk_on_invalidate', False)
31 self.timeout = float(self._ui.config( 35 self.timeout = float(self._ui.config(
33 37
34 def get(self): 38 def get(self):
35 try: 39 try:
36 file = self._vfs('fsmonitor.state', 'rb') 40 file = self._vfs('fsmonitor.state', 'rb')
37 except IOError as inst: 41 except IOError as inst:
42 self._identity = util.filestat(None)
38 if inst.errno != errno.ENOENT: 43 if inst.errno != errno.ENOENT:
39 raise 44 raise
40 return None, None, None 45 return None, None, None
46
47 self._identity = util.filestat.fromfp(file)
41 48
42 versionbytes = file.read(4) 49 versionbytes = file.read(4)
43 if len(versionbytes) < 4: 50 if len(versionbytes) < 4:
44 self._ui.log( 51 self._ui.log(
45 'fsmonitor', 'fsmonitor: state file only has %d bytes, ' 52 'fsmonitor', 'fsmonitor: state file only has %d bytes, '
88 def set(self, clock, ignorehash, notefiles): 95 def set(self, clock, ignorehash, notefiles):
89 if clock is None: 96 if clock is None:
90 self.invalidate() 97 self.invalidate()
91 return 98 return
92 99
100 # Read the identity from the file on disk rather than from the open file
101 # pointer below, because the latter is actually a brand new file.
102 identity = util.filestat.frompath(self._vfs.join('fsmonitor.state'))
103 if identity != self._identity:
104 self._ui.debug('skip updating fsmonitor.state: identity mismatch\n')
105 return
106
93 try: 107 try:
94 file = self._vfs('fsmonitor.state', 'wb', atomictemp=True) 108 file = self._vfs('fsmonitor.state', 'wb', atomictemp=True,
109 checkambig=True)
95 except (IOError, OSError): 110 except (IOError, OSError):
96 self._ui.warn(_("warning: unable to write out fsmonitor state\n")) 111 self._ui.warn(_("warning: unable to write out fsmonitor state\n"))
97 return 112 return
98 113
99 with file: 114 with file:
109 try: 124 try:
110 os.unlink(os.path.join(self._rootdir, '.hg', 'fsmonitor.state')) 125 os.unlink(os.path.join(self._rootdir, '.hg', 'fsmonitor.state'))
111 except OSError as inst: 126 except OSError as inst:
112 if inst.errno != errno.ENOENT: 127 if inst.errno != errno.ENOENT:
113 raise 128 raise
129 self._identity = util.filestat(None)
114 130
115 def setlastclock(self, clock): 131 def setlastclock(self, clock):
116 self._lastclock = clock 132 self._lastclock = clock
117 133
118 def getlastclock(self): 134 def getlastclock(self):