##// END OF EJS Templates
inotify: inotify.server.walk*() remove unnecessary var...
Nicolas Dumazet -
r8324:b923d599 default
parent child Browse files
Show More
@@ -1,762 +1,756 b''
1 # server.py - inotify status server
1 # server.py - inotify status server
2 #
2 #
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10 from mercurial import osutil, util
10 from mercurial import osutil, util
11 import common
11 import common
12 import errno, os, select, socket, stat, struct, sys, tempfile, time
12 import errno, os, select, socket, stat, struct, sys, tempfile, time
13
13
14 try:
14 try:
15 import linux as inotify
15 import linux as inotify
16 from linux import watcher
16 from linux import watcher
17 except ImportError:
17 except ImportError:
18 raise
18 raise
19
19
20 class AlreadyStartedException(Exception): pass
20 class AlreadyStartedException(Exception): pass
21
21
22 def join(a, b):
22 def join(a, b):
23 if a:
23 if a:
24 if a[-1] == '/':
24 if a[-1] == '/':
25 return a + b
25 return a + b
26 return a + '/' + b
26 return a + '/' + b
27 return b
27 return b
28
28
29 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
29 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
30
30
31 def walkrepodirs(repo):
31 def walkrepodirs(repo):
32 '''Iterate over all subdirectories of this repo.
32 '''Iterate over all subdirectories of this repo.
33 Exclude the .hg directory, any nested repos, and ignored dirs.'''
33 Exclude the .hg directory, any nested repos, and ignored dirs.'''
34 rootslash = repo.root + os.sep
34 rootslash = repo.root + os.sep
35
35
36 def walkit(dirname, top):
36 def walkit(dirname, top):
37 fullpath = rootslash + dirname
37 fullpath = rootslash + dirname
38 hginside = False
39 try:
38 try:
40 for name, kind in osutil.listdir(fullpath):
39 for name, kind in osutil.listdir(fullpath):
41 if kind == stat.S_IFDIR:
40 if kind == stat.S_IFDIR:
42 if name == '.hg':
41 if name == '.hg':
43 hginside = True
44 if not top:
42 if not top:
45 return
43 return
46 else:
44 else:
47 d = join(dirname, name)
45 d = join(dirname, name)
48 if repo.dirstate._ignore(d):
46 if repo.dirstate._ignore(d):
49 continue
47 continue
50 for subdir in walkit(d, False):
48 for subdir in walkit(d, False):
51 yield subdir
49 yield subdir
52 except OSError, err:
50 except OSError, err:
53 if err.errno not in walk_ignored_errors:
51 if err.errno not in walk_ignored_errors:
54 raise
52 raise
55 if top or not hginside:
53 yield fullpath
56 yield fullpath
57
54
58 return walkit('', True)
55 return walkit('', True)
59
56
60 def walk(repo, root):
57 def walk(repo, root):
61 '''Like os.walk, but only yields regular files.'''
58 '''Like os.walk, but only yields regular files.'''
62
59
63 # This function is critical to performance during startup.
60 # This function is critical to performance during startup.
64
61
65 rootslash = repo.root + os.sep
62 rootslash = repo.root + os.sep
66
63
67 def walkit(root, reporoot):
64 def walkit(root, reporoot):
68 files, dirs = [], []
65 files, dirs = [], []
69 hginside = False
70
66
71 try:
67 try:
72 fullpath = rootslash + root
68 fullpath = rootslash + root
73 for name, kind in osutil.listdir(fullpath):
69 for name, kind in osutil.listdir(fullpath):
74 if kind == stat.S_IFDIR:
70 if kind == stat.S_IFDIR:
75 if name == '.hg':
71 if name == '.hg':
76 hginside = True
77 if reporoot:
72 if reporoot:
78 continue
73 continue
79 else:
74 else:
80 return
75 return
81 dirs.append(name)
76 dirs.append(name)
82 elif kind in (stat.S_IFREG, stat.S_IFLNK):
77 elif kind in (stat.S_IFREG, stat.S_IFLNK):
83 files.append((name, kind))
78 files.append((name, kind))
84 if reporoot or not hginside:
79 yield fullpath, dirs, files
85 yield fullpath, dirs, files
86
80
87 for subdir in dirs:
81 for subdir in dirs:
88 path = join(root, subdir)
82 path = join(root, subdir)
89 if repo.dirstate._ignore(path):
83 if repo.dirstate._ignore(path):
90 continue
84 continue
91 for result in walkit(path, False):
85 for result in walkit(path, False):
92 yield result
86 yield result
93 except OSError, err:
87 except OSError, err:
94 if err.errno not in walk_ignored_errors:
88 if err.errno not in walk_ignored_errors:
95 raise
89 raise
96
90
97 return walkit(root, root == '')
91 return walkit(root, root == '')
98
92
99 def _explain_watch_limit(ui, repo, count):
93 def _explain_watch_limit(ui, repo, count):
100 path = '/proc/sys/fs/inotify/max_user_watches'
94 path = '/proc/sys/fs/inotify/max_user_watches'
101 try:
95 try:
102 limit = int(file(path).read())
96 limit = int(file(path).read())
103 except IOError, err:
97 except IOError, err:
104 if err.errno != errno.ENOENT:
98 if err.errno != errno.ENOENT:
105 raise
99 raise
106 raise util.Abort(_('this system does not seem to '
100 raise util.Abort(_('this system does not seem to '
107 'support inotify'))
101 'support inotify'))
108 ui.warn(_('*** the current per-user limit on the number '
102 ui.warn(_('*** the current per-user limit on the number '
109 'of inotify watches is %s\n') % limit)
103 'of inotify watches is %s\n') % limit)
110 ui.warn(_('*** this limit is too low to watch every '
104 ui.warn(_('*** this limit is too low to watch every '
111 'directory in this repository\n'))
105 'directory in this repository\n'))
112 ui.warn(_('*** counting directories: '))
106 ui.warn(_('*** counting directories: '))
113 ndirs = len(list(walkrepodirs(repo)))
107 ndirs = len(list(walkrepodirs(repo)))
114 ui.warn(_('found %d\n') % ndirs)
108 ui.warn(_('found %d\n') % ndirs)
115 newlimit = min(limit, 1024)
109 newlimit = min(limit, 1024)
116 while newlimit < ((limit + ndirs) * 1.1):
110 while newlimit < ((limit + ndirs) * 1.1):
117 newlimit *= 2
111 newlimit *= 2
118 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
112 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
119 (limit, newlimit))
113 (limit, newlimit))
120 ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
114 ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
121 raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
115 raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
122 % repo.root)
116 % repo.root)
123
117
124 class Watcher(object):
118 class Watcher(object):
125 poll_events = select.POLLIN
119 poll_events = select.POLLIN
126 statuskeys = 'almr!?'
120 statuskeys = 'almr!?'
127
121
128 def __init__(self, ui, repo, master):
122 def __init__(self, ui, repo, master):
129 self.ui = ui
123 self.ui = ui
130 self.repo = repo
124 self.repo = repo
131 self.wprefix = self.repo.wjoin('')
125 self.wprefix = self.repo.wjoin('')
132 self.timeout = None
126 self.timeout = None
133 self.master = master
127 self.master = master
134 self.mask = (
128 self.mask = (
135 inotify.IN_ATTRIB |
129 inotify.IN_ATTRIB |
136 inotify.IN_CREATE |
130 inotify.IN_CREATE |
137 inotify.IN_DELETE |
131 inotify.IN_DELETE |
138 inotify.IN_DELETE_SELF |
132 inotify.IN_DELETE_SELF |
139 inotify.IN_MODIFY |
133 inotify.IN_MODIFY |
140 inotify.IN_MOVED_FROM |
134 inotify.IN_MOVED_FROM |
141 inotify.IN_MOVED_TO |
135 inotify.IN_MOVED_TO |
142 inotify.IN_MOVE_SELF |
136 inotify.IN_MOVE_SELF |
143 inotify.IN_ONLYDIR |
137 inotify.IN_ONLYDIR |
144 inotify.IN_UNMOUNT |
138 inotify.IN_UNMOUNT |
145 0)
139 0)
146 try:
140 try:
147 self.watcher = watcher.Watcher()
141 self.watcher = watcher.Watcher()
148 except OSError, err:
142 except OSError, err:
149 raise util.Abort(_('inotify service not available: %s') %
143 raise util.Abort(_('inotify service not available: %s') %
150 err.strerror)
144 err.strerror)
151 self.threshold = watcher.Threshold(self.watcher)
145 self.threshold = watcher.Threshold(self.watcher)
152 self.registered = True
146 self.registered = True
153 self.fileno = self.watcher.fileno
147 self.fileno = self.watcher.fileno
154
148
155 self.repo.dirstate.__class__.inotifyserver = True
149 self.repo.dirstate.__class__.inotifyserver = True
156
150
157 self.tree = {}
151 self.tree = {}
158 self.statcache = {}
152 self.statcache = {}
159 self.statustrees = dict([(s, {}) for s in self.statuskeys])
153 self.statustrees = dict([(s, {}) for s in self.statuskeys])
160
154
161 self.watches = 0
155 self.watches = 0
162 self.last_event = None
156 self.last_event = None
163
157
164 self.eventq = {}
158 self.eventq = {}
165 self.deferred = 0
159 self.deferred = 0
166
160
167 self.ds_info = self.dirstate_info()
161 self.ds_info = self.dirstate_info()
168 self.scan()
162 self.scan()
169
163
170 def event_time(self):
164 def event_time(self):
171 last = self.last_event
165 last = self.last_event
172 now = time.time()
166 now = time.time()
173 self.last_event = now
167 self.last_event = now
174
168
175 if last is None:
169 if last is None:
176 return 'start'
170 return 'start'
177 delta = now - last
171 delta = now - last
178 if delta < 5:
172 if delta < 5:
179 return '+%.3f' % delta
173 return '+%.3f' % delta
180 if delta < 50:
174 if delta < 50:
181 return '+%.2f' % delta
175 return '+%.2f' % delta
182 return '+%.1f' % delta
176 return '+%.1f' % delta
183
177
184 def dirstate_info(self):
178 def dirstate_info(self):
185 try:
179 try:
186 st = os.lstat(self.repo.join('dirstate'))
180 st = os.lstat(self.repo.join('dirstate'))
187 return st.st_mtime, st.st_ino
181 return st.st_mtime, st.st_ino
188 except OSError, err:
182 except OSError, err:
189 if err.errno != errno.ENOENT:
183 if err.errno != errno.ENOENT:
190 raise
184 raise
191 return 0, 0
185 return 0, 0
192
186
193 def add_watch(self, path, mask):
187 def add_watch(self, path, mask):
194 if not path:
188 if not path:
195 return
189 return
196 if self.watcher.path(path) is None:
190 if self.watcher.path(path) is None:
197 if self.ui.debugflag:
191 if self.ui.debugflag:
198 self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
192 self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
199 try:
193 try:
200 self.watcher.add(path, mask)
194 self.watcher.add(path, mask)
201 self.watches += 1
195 self.watches += 1
202 except OSError, err:
196 except OSError, err:
203 if err.errno in (errno.ENOENT, errno.ENOTDIR):
197 if err.errno in (errno.ENOENT, errno.ENOTDIR):
204 return
198 return
205 if err.errno != errno.ENOSPC:
199 if err.errno != errno.ENOSPC:
206 raise
200 raise
207 _explain_watch_limit(self.ui, self.repo, self.watches)
201 _explain_watch_limit(self.ui, self.repo, self.watches)
208
202
209 def setup(self):
203 def setup(self):
210 self.ui.note(_('watching directories under %r\n') % self.repo.root)
204 self.ui.note(_('watching directories under %r\n') % self.repo.root)
211 self.add_watch(self.repo.path, inotify.IN_DELETE)
205 self.add_watch(self.repo.path, inotify.IN_DELETE)
212 self.check_dirstate()
206 self.check_dirstate()
213
207
214 def wpath(self, evt):
208 def wpath(self, evt):
215 path = evt.fullpath
209 path = evt.fullpath
216 if path == self.repo.root:
210 if path == self.repo.root:
217 return ''
211 return ''
218 if path.startswith(self.wprefix):
212 if path.startswith(self.wprefix):
219 return path[len(self.wprefix):]
213 return path[len(self.wprefix):]
220 raise 'wtf? ' + path
214 raise 'wtf? ' + path
221
215
222 def dir(self, tree, path):
216 def dir(self, tree, path):
223 if path:
217 if path:
224 for name in path.split('/'):
218 for name in path.split('/'):
225 tree.setdefault(name, {})
219 tree.setdefault(name, {})
226 tree = tree[name]
220 tree = tree[name]
227 return tree
221 return tree
228
222
229 def lookup(self, path, tree):
223 def lookup(self, path, tree):
230 if path:
224 if path:
231 try:
225 try:
232 for name in path.split('/'):
226 for name in path.split('/'):
233 tree = tree[name]
227 tree = tree[name]
234 except KeyError:
228 except KeyError:
235 return 'x'
229 return 'x'
236 except TypeError:
230 except TypeError:
237 return 'd'
231 return 'd'
238 return tree
232 return tree
239
233
240 def split(self, path):
234 def split(self, path):
241 c = path.rfind('/')
235 c = path.rfind('/')
242 if c == -1:
236 if c == -1:
243 return '', path
237 return '', path
244 return path[:c], path[c+1:]
238 return path[:c], path[c+1:]
245
239
246 def filestatus(self, fn, st):
240 def filestatus(self, fn, st):
247 try:
241 try:
248 type_, mode, size, time = self.repo.dirstate._map[fn][:4]
242 type_, mode, size, time = self.repo.dirstate._map[fn][:4]
249 except KeyError:
243 except KeyError:
250 type_ = '?'
244 type_ = '?'
251 if type_ == 'n':
245 if type_ == 'n':
252 if not st:
246 if not st:
253 return '!'
247 return '!'
254 st_mode, st_size, st_mtime = st
248 st_mode, st_size, st_mtime = st
255 if size == -1:
249 if size == -1:
256 return 'l'
250 return 'l'
257 if size and (size != st_size or (mode ^ st_mode) & 0100):
251 if size and (size != st_size or (mode ^ st_mode) & 0100):
258 return 'm'
252 return 'm'
259 if time != int(st_mtime):
253 if time != int(st_mtime):
260 return 'l'
254 return 'l'
261 return 'n'
255 return 'n'
262 if type_ in 'ma' and not st:
256 if type_ in 'ma' and not st:
263 return '!'
257 return '!'
264 if type_ == '?' and self.repo.dirstate._ignore(fn):
258 if type_ == '?' and self.repo.dirstate._ignore(fn):
265 return 'i'
259 return 'i'
266 return type_
260 return type_
267
261
268 def updatestatus(self, wfn, st=None, status=None):
262 def updatestatus(self, wfn, st=None, status=None):
269 if st:
263 if st:
270 status = self.filestatus(wfn, st)
264 status = self.filestatus(wfn, st)
271 else:
265 else:
272 self.statcache.pop(wfn, None)
266 self.statcache.pop(wfn, None)
273 root, fn = self.split(wfn)
267 root, fn = self.split(wfn)
274 d = self.dir(self.tree, root)
268 d = self.dir(self.tree, root)
275 oldstatus = d.get(fn)
269 oldstatus = d.get(fn)
276 isdir = False
270 isdir = False
277 if oldstatus:
271 if oldstatus:
278 try:
272 try:
279 if not status:
273 if not status:
280 if oldstatus in 'almn':
274 if oldstatus in 'almn':
281 status = '!'
275 status = '!'
282 elif oldstatus == 'r':
276 elif oldstatus == 'r':
283 status = 'r'
277 status = 'r'
284 except TypeError:
278 except TypeError:
285 # oldstatus may be a dict left behind by a deleted
279 # oldstatus may be a dict left behind by a deleted
286 # directory
280 # directory
287 isdir = True
281 isdir = True
288 else:
282 else:
289 if oldstatus in self.statuskeys and oldstatus != status:
283 if oldstatus in self.statuskeys and oldstatus != status:
290 del self.dir(self.statustrees[oldstatus], root)[fn]
284 del self.dir(self.statustrees[oldstatus], root)[fn]
291 if self.ui.debugflag and oldstatus != status:
285 if self.ui.debugflag and oldstatus != status:
292 if isdir:
286 if isdir:
293 self.ui.note(_('status: %r dir(%d) -> %s\n') %
287 self.ui.note(_('status: %r dir(%d) -> %s\n') %
294 (wfn, len(oldstatus), status))
288 (wfn, len(oldstatus), status))
295 else:
289 else:
296 self.ui.note(_('status: %r %s -> %s\n') %
290 self.ui.note(_('status: %r %s -> %s\n') %
297 (wfn, oldstatus, status))
291 (wfn, oldstatus, status))
298 if not isdir:
292 if not isdir:
299 if status and status != 'i':
293 if status and status != 'i':
300 d[fn] = status
294 d[fn] = status
301 if status in self.statuskeys:
295 if status in self.statuskeys:
302 dd = self.dir(self.statustrees[status], root)
296 dd = self.dir(self.statustrees[status], root)
303 if oldstatus != status or fn not in dd:
297 if oldstatus != status or fn not in dd:
304 dd[fn] = status
298 dd[fn] = status
305 else:
299 else:
306 d.pop(fn, None)
300 d.pop(fn, None)
307 elif not status:
301 elif not status:
308 # a directory is being removed, check its contents
302 # a directory is being removed, check its contents
309 for subfile, b in oldstatus.copy().iteritems():
303 for subfile, b in oldstatus.copy().iteritems():
310 self.updatestatus(wfn + '/' + subfile, None)
304 self.updatestatus(wfn + '/' + subfile, None)
311
305
312
306
313 def check_deleted(self, key):
307 def check_deleted(self, key):
314 # Files that had been deleted but were present in the dirstate
308 # Files that had been deleted but were present in the dirstate
315 # may have vanished from the dirstate; we must clean them up.
309 # may have vanished from the dirstate; we must clean them up.
316 nuke = []
310 nuke = []
317 for wfn, ignore in self.walk(key, self.statustrees[key]):
311 for wfn, ignore in self.walk(key, self.statustrees[key]):
318 if wfn not in self.repo.dirstate:
312 if wfn not in self.repo.dirstate:
319 nuke.append(wfn)
313 nuke.append(wfn)
320 for wfn in nuke:
314 for wfn in nuke:
321 root, fn = self.split(wfn)
315 root, fn = self.split(wfn)
322 del self.dir(self.statustrees[key], root)[fn]
316 del self.dir(self.statustrees[key], root)[fn]
323 del self.dir(self.tree, root)[fn]
317 del self.dir(self.tree, root)[fn]
324
318
325 def scan(self, topdir=''):
319 def scan(self, topdir=''):
326 self.handle_timeout()
320 self.handle_timeout()
327 ds = self.repo.dirstate._map.copy()
321 ds = self.repo.dirstate._map.copy()
328 self.add_watch(join(self.repo.root, topdir), self.mask)
322 self.add_watch(join(self.repo.root, topdir), self.mask)
329 for root, dirs, entries in walk(self.repo, topdir):
323 for root, dirs, entries in walk(self.repo, topdir):
330 for d in dirs:
324 for d in dirs:
331 self.add_watch(join(root, d), self.mask)
325 self.add_watch(join(root, d), self.mask)
332 wroot = root[len(self.wprefix):]
326 wroot = root[len(self.wprefix):]
333 d = self.dir(self.tree, wroot)
327 d = self.dir(self.tree, wroot)
334 for fn, kind in entries:
328 for fn, kind in entries:
335 wfn = join(wroot, fn)
329 wfn = join(wroot, fn)
336 self.updatestatus(wfn, self.getstat(wfn))
330 self.updatestatus(wfn, self.getstat(wfn))
337 ds.pop(wfn, None)
331 ds.pop(wfn, None)
338 wtopdir = topdir
332 wtopdir = topdir
339 if wtopdir and wtopdir[-1] != '/':
333 if wtopdir and wtopdir[-1] != '/':
340 wtopdir += '/'
334 wtopdir += '/'
341 for wfn, state in ds.iteritems():
335 for wfn, state in ds.iteritems():
342 if not wfn.startswith(wtopdir):
336 if not wfn.startswith(wtopdir):
343 continue
337 continue
344 try:
338 try:
345 st = self.stat(wfn)
339 st = self.stat(wfn)
346 except OSError:
340 except OSError:
347 status = state[0]
341 status = state[0]
348 self.updatestatus(wfn, None, status=status)
342 self.updatestatus(wfn, None, status=status)
349 else:
343 else:
350 self.updatestatus(wfn, st)
344 self.updatestatus(wfn, st)
351 self.check_deleted('!')
345 self.check_deleted('!')
352 self.check_deleted('r')
346 self.check_deleted('r')
353
347
354 def check_dirstate(self):
348 def check_dirstate(self):
355 ds_info = self.dirstate_info()
349 ds_info = self.dirstate_info()
356 if ds_info == self.ds_info:
350 if ds_info == self.ds_info:
357 return
351 return
358 self.ds_info = ds_info
352 self.ds_info = ds_info
359 if not self.ui.debugflag:
353 if not self.ui.debugflag:
360 self.last_event = None
354 self.last_event = None
361 self.ui.note(_('%s dirstate reload\n') % self.event_time())
355 self.ui.note(_('%s dirstate reload\n') % self.event_time())
362 self.repo.dirstate.invalidate()
356 self.repo.dirstate.invalidate()
363 self.scan()
357 self.scan()
364 self.ui.note(_('%s end dirstate reload\n') % self.event_time())
358 self.ui.note(_('%s end dirstate reload\n') % self.event_time())
365
359
366 def walk(self, states, tree, prefix=''):
360 def walk(self, states, tree, prefix=''):
367 # This is the "inner loop" when talking to the client.
361 # This is the "inner loop" when talking to the client.
368
362
369 for name, val in tree.iteritems():
363 for name, val in tree.iteritems():
370 path = join(prefix, name)
364 path = join(prefix, name)
371 try:
365 try:
372 if val in states:
366 if val in states:
373 yield path, val
367 yield path, val
374 except TypeError:
368 except TypeError:
375 for p in self.walk(states, val, path):
369 for p in self.walk(states, val, path):
376 yield p
370 yield p
377
371
378 def update_hgignore(self):
372 def update_hgignore(self):
379 # An update of the ignore file can potentially change the
373 # An update of the ignore file can potentially change the
380 # states of all unknown and ignored files.
374 # states of all unknown and ignored files.
381
375
382 # XXX If the user has other ignore files outside the repo, or
376 # XXX If the user has other ignore files outside the repo, or
383 # changes their list of ignore files at run time, we'll
377 # changes their list of ignore files at run time, we'll
384 # potentially never see changes to them. We could get the
378 # potentially never see changes to them. We could get the
385 # client to report to us what ignore data they're using.
379 # client to report to us what ignore data they're using.
386 # But it's easier to do nothing than to open that can of
380 # But it's easier to do nothing than to open that can of
387 # worms.
381 # worms.
388
382
389 if '_ignore' in self.repo.dirstate.__dict__:
383 if '_ignore' in self.repo.dirstate.__dict__:
390 delattr(self.repo.dirstate, '_ignore')
384 delattr(self.repo.dirstate, '_ignore')
391 self.ui.note(_('rescanning due to .hgignore change\n'))
385 self.ui.note(_('rescanning due to .hgignore change\n'))
392 self.scan()
386 self.scan()
393
387
394 def getstat(self, wpath):
388 def getstat(self, wpath):
395 try:
389 try:
396 return self.statcache[wpath]
390 return self.statcache[wpath]
397 except KeyError:
391 except KeyError:
398 try:
392 try:
399 return self.stat(wpath)
393 return self.stat(wpath)
400 except OSError, err:
394 except OSError, err:
401 if err.errno != errno.ENOENT:
395 if err.errno != errno.ENOENT:
402 raise
396 raise
403
397
404 def stat(self, wpath):
398 def stat(self, wpath):
405 try:
399 try:
406 st = os.lstat(join(self.wprefix, wpath))
400 st = os.lstat(join(self.wprefix, wpath))
407 ret = st.st_mode, st.st_size, st.st_mtime
401 ret = st.st_mode, st.st_size, st.st_mtime
408 self.statcache[wpath] = ret
402 self.statcache[wpath] = ret
409 return ret
403 return ret
410 except OSError:
404 except OSError:
411 self.statcache.pop(wpath, None)
405 self.statcache.pop(wpath, None)
412 raise
406 raise
413
407
414 def created(self, wpath):
408 def created(self, wpath):
415 if wpath == '.hgignore':
409 if wpath == '.hgignore':
416 self.update_hgignore()
410 self.update_hgignore()
417 try:
411 try:
418 st = self.stat(wpath)
412 st = self.stat(wpath)
419 if stat.S_ISREG(st[0]):
413 if stat.S_ISREG(st[0]):
420 self.updatestatus(wpath, st)
414 self.updatestatus(wpath, st)
421 except OSError:
415 except OSError:
422 pass
416 pass
423
417
424 def modified(self, wpath):
418 def modified(self, wpath):
425 if wpath == '.hgignore':
419 if wpath == '.hgignore':
426 self.update_hgignore()
420 self.update_hgignore()
427 try:
421 try:
428 st = self.stat(wpath)
422 st = self.stat(wpath)
429 if stat.S_ISREG(st[0]):
423 if stat.S_ISREG(st[0]):
430 if self.repo.dirstate[wpath] in 'lmn':
424 if self.repo.dirstate[wpath] in 'lmn':
431 self.updatestatus(wpath, st)
425 self.updatestatus(wpath, st)
432 except OSError:
426 except OSError:
433 pass
427 pass
434
428
435 def deleted(self, wpath):
429 def deleted(self, wpath):
436 if wpath == '.hgignore':
430 if wpath == '.hgignore':
437 self.update_hgignore()
431 self.update_hgignore()
438 elif wpath.startswith('.hg/'):
432 elif wpath.startswith('.hg/'):
439 if wpath == '.hg/wlock':
433 if wpath == '.hg/wlock':
440 self.check_dirstate()
434 self.check_dirstate()
441 return
435 return
442
436
443 self.updatestatus(wpath, None)
437 self.updatestatus(wpath, None)
444
438
445 def schedule_work(self, wpath, evt):
439 def schedule_work(self, wpath, evt):
446 self.eventq.setdefault(wpath, [])
440 self.eventq.setdefault(wpath, [])
447 prev = self.eventq[wpath]
441 prev = self.eventq[wpath]
448 try:
442 try:
449 if prev and evt == 'm' and prev[-1] in 'cm':
443 if prev and evt == 'm' and prev[-1] in 'cm':
450 return
444 return
451 self.eventq[wpath].append(evt)
445 self.eventq[wpath].append(evt)
452 finally:
446 finally:
453 self.deferred += 1
447 self.deferred += 1
454 self.timeout = 250
448 self.timeout = 250
455
449
456 def deferred_event(self, wpath, evt):
450 def deferred_event(self, wpath, evt):
457 if evt == 'c':
451 if evt == 'c':
458 self.created(wpath)
452 self.created(wpath)
459 elif evt == 'm':
453 elif evt == 'm':
460 self.modified(wpath)
454 self.modified(wpath)
461 elif evt == 'd':
455 elif evt == 'd':
462 self.deleted(wpath)
456 self.deleted(wpath)
463
457
464 def process_create(self, wpath, evt):
458 def process_create(self, wpath, evt):
465 if self.ui.debugflag:
459 if self.ui.debugflag:
466 self.ui.note(_('%s event: created %s\n') %
460 self.ui.note(_('%s event: created %s\n') %
467 (self.event_time(), wpath))
461 (self.event_time(), wpath))
468
462
469 if evt.mask & inotify.IN_ISDIR:
463 if evt.mask & inotify.IN_ISDIR:
470 self.scan(wpath)
464 self.scan(wpath)
471 else:
465 else:
472 self.schedule_work(wpath, 'c')
466 self.schedule_work(wpath, 'c')
473
467
474 def process_delete(self, wpath, evt):
468 def process_delete(self, wpath, evt):
475 if self.ui.debugflag:
469 if self.ui.debugflag:
476 self.ui.note(_('%s event: deleted %s\n') %
470 self.ui.note(_('%s event: deleted %s\n') %
477 (self.event_time(), wpath))
471 (self.event_time(), wpath))
478
472
479 if evt.mask & inotify.IN_ISDIR:
473 if evt.mask & inotify.IN_ISDIR:
480 self.scan(wpath)
474 self.scan(wpath)
481 self.schedule_work(wpath, 'd')
475 self.schedule_work(wpath, 'd')
482
476
483 def process_modify(self, wpath, evt):
477 def process_modify(self, wpath, evt):
484 if self.ui.debugflag:
478 if self.ui.debugflag:
485 self.ui.note(_('%s event: modified %s\n') %
479 self.ui.note(_('%s event: modified %s\n') %
486 (self.event_time(), wpath))
480 (self.event_time(), wpath))
487
481
488 if not (evt.mask & inotify.IN_ISDIR):
482 if not (evt.mask & inotify.IN_ISDIR):
489 self.schedule_work(wpath, 'm')
483 self.schedule_work(wpath, 'm')
490
484
491 def process_unmount(self, evt):
485 def process_unmount(self, evt):
492 self.ui.warn(_('filesystem containing %s was unmounted\n') %
486 self.ui.warn(_('filesystem containing %s was unmounted\n') %
493 evt.fullpath)
487 evt.fullpath)
494 sys.exit(0)
488 sys.exit(0)
495
489
496 def handle_event(self, fd, event):
490 def handle_event(self, fd, event):
497 if self.ui.debugflag:
491 if self.ui.debugflag:
498 self.ui.note(_('%s readable: %d bytes\n') %
492 self.ui.note(_('%s readable: %d bytes\n') %
499 (self.event_time(), self.threshold.readable()))
493 (self.event_time(), self.threshold.readable()))
500 if not self.threshold():
494 if not self.threshold():
501 if self.registered:
495 if self.registered:
502 if self.ui.debugflag:
496 if self.ui.debugflag:
503 self.ui.note(_('%s below threshold - unhooking\n') %
497 self.ui.note(_('%s below threshold - unhooking\n') %
504 (self.event_time()))
498 (self.event_time()))
505 self.master.poll.unregister(fd)
499 self.master.poll.unregister(fd)
506 self.registered = False
500 self.registered = False
507 self.timeout = 250
501 self.timeout = 250
508 else:
502 else:
509 self.read_events()
503 self.read_events()
510
504
511 def read_events(self, bufsize=None):
505 def read_events(self, bufsize=None):
512 events = self.watcher.read(bufsize)
506 events = self.watcher.read(bufsize)
513 if self.ui.debugflag:
507 if self.ui.debugflag:
514 self.ui.note(_('%s reading %d events\n') %
508 self.ui.note(_('%s reading %d events\n') %
515 (self.event_time(), len(events)))
509 (self.event_time(), len(events)))
516 for evt in events:
510 for evt in events:
517 wpath = self.wpath(evt)
511 wpath = self.wpath(evt)
518 if evt.mask & inotify.IN_UNMOUNT:
512 if evt.mask & inotify.IN_UNMOUNT:
519 self.process_unmount(wpath, evt)
513 self.process_unmount(wpath, evt)
520 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
514 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
521 self.process_modify(wpath, evt)
515 self.process_modify(wpath, evt)
522 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
516 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
523 inotify.IN_MOVED_FROM):
517 inotify.IN_MOVED_FROM):
524 self.process_delete(wpath, evt)
518 self.process_delete(wpath, evt)
525 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
519 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
526 self.process_create(wpath, evt)
520 self.process_create(wpath, evt)
527
521
528 def handle_timeout(self):
522 def handle_timeout(self):
529 if not self.registered:
523 if not self.registered:
530 if self.ui.debugflag:
524 if self.ui.debugflag:
531 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
525 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
532 (self.event_time(), self.threshold.readable()))
526 (self.event_time(), self.threshold.readable()))
533 self.read_events(0)
527 self.read_events(0)
534 self.master.poll.register(self, select.POLLIN)
528 self.master.poll.register(self, select.POLLIN)
535 self.registered = True
529 self.registered = True
536
530
537 if self.eventq:
531 if self.eventq:
538 if self.ui.debugflag:
532 if self.ui.debugflag:
539 self.ui.note(_('%s processing %d deferred events as %d\n') %
533 self.ui.note(_('%s processing %d deferred events as %d\n') %
540 (self.event_time(), self.deferred,
534 (self.event_time(), self.deferred,
541 len(self.eventq)))
535 len(self.eventq)))
542 for wpath, evts in sorted(self.eventq.iteritems()):
536 for wpath, evts in sorted(self.eventq.iteritems()):
543 for evt in evts:
537 for evt in evts:
544 self.deferred_event(wpath, evt)
538 self.deferred_event(wpath, evt)
545 self.eventq.clear()
539 self.eventq.clear()
546 self.deferred = 0
540 self.deferred = 0
547 self.timeout = None
541 self.timeout = None
548
542
549 def shutdown(self):
543 def shutdown(self):
550 self.watcher.close()
544 self.watcher.close()
551
545
552 class Server(object):
546 class Server(object):
553 poll_events = select.POLLIN
547 poll_events = select.POLLIN
554
548
555 def __init__(self, ui, repo, watcher, timeout):
549 def __init__(self, ui, repo, watcher, timeout):
556 self.ui = ui
550 self.ui = ui
557 self.repo = repo
551 self.repo = repo
558 self.watcher = watcher
552 self.watcher = watcher
559 self.timeout = timeout
553 self.timeout = timeout
560 self.sock = socket.socket(socket.AF_UNIX)
554 self.sock = socket.socket(socket.AF_UNIX)
561 self.sockpath = self.repo.join('inotify.sock')
555 self.sockpath = self.repo.join('inotify.sock')
562 self.realsockpath = None
556 self.realsockpath = None
563 try:
557 try:
564 self.sock.bind(self.sockpath)
558 self.sock.bind(self.sockpath)
565 except socket.error, err:
559 except socket.error, err:
566 if err[0] == errno.EADDRINUSE:
560 if err[0] == errno.EADDRINUSE:
567 raise AlreadyStartedException(_('could not start server: %s')
561 raise AlreadyStartedException(_('could not start server: %s')
568 % err[1])
562 % err[1])
569 if err[0] == "AF_UNIX path too long":
563 if err[0] == "AF_UNIX path too long":
570 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
564 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
571 self.realsockpath = os.path.join(tempdir, "inotify.sock")
565 self.realsockpath = os.path.join(tempdir, "inotify.sock")
572 try:
566 try:
573 self.sock.bind(self.realsockpath)
567 self.sock.bind(self.realsockpath)
574 os.symlink(self.realsockpath, self.sockpath)
568 os.symlink(self.realsockpath, self.sockpath)
575 except (OSError, socket.error), inst:
569 except (OSError, socket.error), inst:
576 try:
570 try:
577 os.unlink(self.realsockpath)
571 os.unlink(self.realsockpath)
578 except:
572 except:
579 pass
573 pass
580 os.rmdir(tempdir)
574 os.rmdir(tempdir)
581 if inst.errno == errno.EEXIST:
575 if inst.errno == errno.EEXIST:
582 raise AlreadyStartedException(_('could not start server: %s')
576 raise AlreadyStartedException(_('could not start server: %s')
583 % inst.strerror)
577 % inst.strerror)
584 raise
578 raise
585 else:
579 else:
586 raise
580 raise
587 self.sock.listen(5)
581 self.sock.listen(5)
588 self.fileno = self.sock.fileno
582 self.fileno = self.sock.fileno
589
583
590 def handle_timeout(self):
584 def handle_timeout(self):
591 pass
585 pass
592
586
593 def handle_event(self, fd, event):
587 def handle_event(self, fd, event):
594 sock, addr = self.sock.accept()
588 sock, addr = self.sock.accept()
595
589
596 cs = common.recvcs(sock)
590 cs = common.recvcs(sock)
597 version = ord(cs.read(1))
591 version = ord(cs.read(1))
598
592
599 sock.sendall(chr(common.version))
593 sock.sendall(chr(common.version))
600
594
601 if version != common.version:
595 if version != common.version:
602 self.ui.warn(_('received query from incompatible client '
596 self.ui.warn(_('received query from incompatible client '
603 'version %d\n') % version)
597 'version %d\n') % version)
604 return
598 return
605
599
606 names = cs.read().split('\0')
600 names = cs.read().split('\0')
607
601
608 states = names.pop()
602 states = names.pop()
609
603
610 self.ui.note(_('answering query for %r\n') % states)
604 self.ui.note(_('answering query for %r\n') % states)
611
605
612 if self.watcher.timeout:
606 if self.watcher.timeout:
613 # We got a query while a rescan is pending. Make sure we
607 # We got a query while a rescan is pending. Make sure we
614 # rescan before responding, or we could give back a wrong
608 # rescan before responding, or we could give back a wrong
615 # answer.
609 # answer.
616 self.watcher.handle_timeout()
610 self.watcher.handle_timeout()
617
611
618 if not names:
612 if not names:
619 def genresult(states, tree):
613 def genresult(states, tree):
620 for fn, state in self.watcher.walk(states, tree):
614 for fn, state in self.watcher.walk(states, tree):
621 yield fn
615 yield fn
622 else:
616 else:
623 def genresult(states, tree):
617 def genresult(states, tree):
624 for fn in names:
618 for fn in names:
625 l = self.watcher.lookup(fn, tree)
619 l = self.watcher.lookup(fn, tree)
626 try:
620 try:
627 if l in states:
621 if l in states:
628 yield fn
622 yield fn
629 except TypeError:
623 except TypeError:
630 for f, s in self.watcher.walk(states, l, fn):
624 for f, s in self.watcher.walk(states, l, fn):
631 yield f
625 yield f
632
626
633 results = ['\0'.join(r) for r in [
627 results = ['\0'.join(r) for r in [
634 genresult('l', self.watcher.statustrees['l']),
628 genresult('l', self.watcher.statustrees['l']),
635 genresult('m', self.watcher.statustrees['m']),
629 genresult('m', self.watcher.statustrees['m']),
636 genresult('a', self.watcher.statustrees['a']),
630 genresult('a', self.watcher.statustrees['a']),
637 genresult('r', self.watcher.statustrees['r']),
631 genresult('r', self.watcher.statustrees['r']),
638 genresult('!', self.watcher.statustrees['!']),
632 genresult('!', self.watcher.statustrees['!']),
639 '?' in states and genresult('?', self.watcher.statustrees['?']) or [],
633 '?' in states and genresult('?', self.watcher.statustrees['?']) or [],
640 [],
634 [],
641 'c' in states and genresult('n', self.watcher.tree) or [],
635 'c' in states and genresult('n', self.watcher.tree) or [],
642 ]]
636 ]]
643
637
644 try:
638 try:
645 try:
639 try:
646 sock.sendall(struct.pack(common.resphdrfmt,
640 sock.sendall(struct.pack(common.resphdrfmt,
647 *map(len, results)))
641 *map(len, results)))
648 sock.sendall(''.join(results))
642 sock.sendall(''.join(results))
649 finally:
643 finally:
650 sock.shutdown(socket.SHUT_WR)
644 sock.shutdown(socket.SHUT_WR)
651 except socket.error, err:
645 except socket.error, err:
652 if err[0] != errno.EPIPE:
646 if err[0] != errno.EPIPE:
653 raise
647 raise
654
648
655 def shutdown(self):
649 def shutdown(self):
656 self.sock.close()
650 self.sock.close()
657 try:
651 try:
658 os.unlink(self.sockpath)
652 os.unlink(self.sockpath)
659 if self.realsockpath:
653 if self.realsockpath:
660 os.unlink(self.realsockpath)
654 os.unlink(self.realsockpath)
661 os.rmdir(os.path.dirname(self.realsockpath))
655 os.rmdir(os.path.dirname(self.realsockpath))
662 except OSError, err:
656 except OSError, err:
663 if err.errno != errno.ENOENT:
657 if err.errno != errno.ENOENT:
664 raise
658 raise
665
659
666 class Master(object):
660 class Master(object):
667 def __init__(self, ui, repo, timeout=None):
661 def __init__(self, ui, repo, timeout=None):
668 self.ui = ui
662 self.ui = ui
669 self.repo = repo
663 self.repo = repo
670 self.poll = select.poll()
664 self.poll = select.poll()
671 self.watcher = Watcher(ui, repo, self)
665 self.watcher = Watcher(ui, repo, self)
672 self.server = Server(ui, repo, self.watcher, timeout)
666 self.server = Server(ui, repo, self.watcher, timeout)
673 self.table = {}
667 self.table = {}
674 for obj in (self.watcher, self.server):
668 for obj in (self.watcher, self.server):
675 fd = obj.fileno()
669 fd = obj.fileno()
676 self.table[fd] = obj
670 self.table[fd] = obj
677 self.poll.register(fd, obj.poll_events)
671 self.poll.register(fd, obj.poll_events)
678
672
679 def register(self, fd, mask):
673 def register(self, fd, mask):
680 self.poll.register(fd, mask)
674 self.poll.register(fd, mask)
681
675
682 def shutdown(self):
676 def shutdown(self):
683 for obj in self.table.itervalues():
677 for obj in self.table.itervalues():
684 obj.shutdown()
678 obj.shutdown()
685
679
686 def run(self):
680 def run(self):
687 self.watcher.setup()
681 self.watcher.setup()
688 self.ui.note(_('finished setup\n'))
682 self.ui.note(_('finished setup\n'))
689 if os.getenv('TIME_STARTUP'):
683 if os.getenv('TIME_STARTUP'):
690 sys.exit(0)
684 sys.exit(0)
691 while True:
685 while True:
692 timeout = None
686 timeout = None
693 timeobj = None
687 timeobj = None
694 for obj in self.table.itervalues():
688 for obj in self.table.itervalues():
695 if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
689 if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
696 timeout, timeobj = obj.timeout, obj
690 timeout, timeobj = obj.timeout, obj
697 try:
691 try:
698 if self.ui.debugflag:
692 if self.ui.debugflag:
699 if timeout is None:
693 if timeout is None:
700 self.ui.note(_('polling: no timeout\n'))
694 self.ui.note(_('polling: no timeout\n'))
701 else:
695 else:
702 self.ui.note(_('polling: %sms timeout\n') % timeout)
696 self.ui.note(_('polling: %sms timeout\n') % timeout)
703 events = self.poll.poll(timeout)
697 events = self.poll.poll(timeout)
704 except select.error, err:
698 except select.error, err:
705 if err[0] == errno.EINTR:
699 if err[0] == errno.EINTR:
706 continue
700 continue
707 raise
701 raise
708 if events:
702 if events:
709 for fd, event in events:
703 for fd, event in events:
710 self.table[fd].handle_event(fd, event)
704 self.table[fd].handle_event(fd, event)
711 elif timeobj:
705 elif timeobj:
712 timeobj.handle_timeout()
706 timeobj.handle_timeout()
713
707
714 def start(ui, repo):
708 def start(ui, repo):
715 def closefds(ignore):
709 def closefds(ignore):
716 # (from python bug #1177468)
710 # (from python bug #1177468)
717 # close all inherited file descriptors
711 # close all inherited file descriptors
718 # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
712 # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
719 # a file descriptor is kept internally as os._urandomfd (created on demand
713 # a file descriptor is kept internally as os._urandomfd (created on demand
720 # the first time os.urandom() is called), and should not be closed
714 # the first time os.urandom() is called), and should not be closed
721 try:
715 try:
722 os.urandom(4)
716 os.urandom(4)
723 urandom_fd = getattr(os, '_urandomfd', None)
717 urandom_fd = getattr(os, '_urandomfd', None)
724 except AttributeError:
718 except AttributeError:
725 urandom_fd = None
719 urandom_fd = None
726 ignore.append(urandom_fd)
720 ignore.append(urandom_fd)
727 for fd in range(3, 256):
721 for fd in range(3, 256):
728 if fd in ignore:
722 if fd in ignore:
729 continue
723 continue
730 try:
724 try:
731 os.close(fd)
725 os.close(fd)
732 except OSError:
726 except OSError:
733 pass
727 pass
734
728
735 m = Master(ui, repo)
729 m = Master(ui, repo)
736 sys.stdout.flush()
730 sys.stdout.flush()
737 sys.stderr.flush()
731 sys.stderr.flush()
738
732
739 pid = os.fork()
733 pid = os.fork()
740 if pid:
734 if pid:
741 return pid
735 return pid
742
736
743 closefds([m.server.fileno(), m.watcher.fileno()])
737 closefds([m.server.fileno(), m.watcher.fileno()])
744 os.setsid()
738 os.setsid()
745
739
746 fd = os.open('/dev/null', os.O_RDONLY)
740 fd = os.open('/dev/null', os.O_RDONLY)
747 os.dup2(fd, 0)
741 os.dup2(fd, 0)
748 if fd > 0:
742 if fd > 0:
749 os.close(fd)
743 os.close(fd)
750
744
751 fd = os.open(ui.config('inotify', 'log', '/dev/null'),
745 fd = os.open(ui.config('inotify', 'log', '/dev/null'),
752 os.O_RDWR | os.O_CREAT | os.O_TRUNC)
746 os.O_RDWR | os.O_CREAT | os.O_TRUNC)
753 os.dup2(fd, 1)
747 os.dup2(fd, 1)
754 os.dup2(fd, 2)
748 os.dup2(fd, 2)
755 if fd > 2:
749 if fd > 2:
756 os.close(fd)
750 os.close(fd)
757
751
758 try:
752 try:
759 m.run()
753 m.run()
760 finally:
754 finally:
761 m.shutdown()
755 m.shutdown()
762 os._exit(0)
756 os._exit(0)
General Comments 0
You need to be logged in to leave comments. Login now