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