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