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