##// END OF EJS Templates
Use relative imports in inotify.server....
Brendan Cully -
r6909:b5a6fce0 default
parent child Browse files
Show More
@@ -1,715 +1,715
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 gettext as _
10 10 from mercurial import osutil, ui, util
11 11 import common
12 12 import errno, os, select, socket, stat, struct, sys, time
13 13
14 14 try:
15 import hgext.inotify.linux as inotify
16 from hgext.inotify.linux import watcher
15 import linux as inotify
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 and (size != st_size or (mode ^ st_mode) & 0100):
257 257 return 'm'
258 258 if time != int(st_mtime):
259 259 return 'l'
260 260 return 'n'
261 261 if type_ in 'ma' and not st:
262 262 return '!'
263 263 if type_ == '?' and self.repo.dirstate._ignore(fn):
264 264 return 'i'
265 265 return type_
266 266
267 267 def updatestatus(self, wfn, st=None, status=None, oldstatus=None):
268 268 if st:
269 269 status = self.filestatus(wfn, st)
270 270 else:
271 271 self.statcache.pop(wfn, None)
272 272 root, fn = self.split(wfn)
273 273 d = self.dir(self.tree, root)
274 274 if oldstatus is None:
275 275 oldstatus = d.get(fn)
276 276 isdir = False
277 277 if oldstatus:
278 278 try:
279 279 if not status:
280 280 if oldstatus in 'almn':
281 281 status = '!'
282 282 elif oldstatus == 'r':
283 283 status = 'r'
284 284 except TypeError:
285 285 # oldstatus may be a dict left behind by a deleted
286 286 # directory
287 287 isdir = True
288 288 else:
289 289 if oldstatus in self.statuskeys and oldstatus != status:
290 290 del self.dir(self.statustrees[oldstatus], root)[fn]
291 291 if self.ui.debugflag and oldstatus != status:
292 292 if isdir:
293 293 self.ui.note('status: %r dir(%d) -> %s\n' %
294 294 (wfn, len(oldstatus), status))
295 295 else:
296 296 self.ui.note('status: %r %s -> %s\n' %
297 297 (wfn, oldstatus, status))
298 298 if not isdir:
299 299 if status and status != 'i':
300 300 d[fn] = status
301 301 if status in self.statuskeys:
302 302 dd = self.dir(self.statustrees[status], root)
303 303 if oldstatus != status or fn not in dd:
304 304 dd[fn] = status
305 305 else:
306 306 d.pop(fn, None)
307 307
308 308 def check_deleted(self, key):
309 309 # Files that had been deleted but were present in the dirstate
310 310 # may have vanished from the dirstate; we must clean them up.
311 311 nuke = []
312 312 for wfn, ignore in self.walk(key, self.statustrees[key]):
313 313 if wfn not in self.repo.dirstate:
314 314 nuke.append(wfn)
315 315 for wfn in nuke:
316 316 root, fn = self.split(wfn)
317 317 del self.dir(self.statustrees[key], root)[fn]
318 318 del self.dir(self.tree, root)[fn]
319 319
320 320 def scan(self, topdir=''):
321 321 self.handle_timeout()
322 322 ds = self.repo.dirstate._map.copy()
323 323 self.add_watch(join(self.repo.root, topdir), self.mask)
324 324 for root, dirs, entries in walk(self.repo, topdir):
325 325 for d in dirs:
326 326 self.add_watch(join(root, d), self.mask)
327 327 wroot = root[len(self.wprefix):]
328 328 d = self.dir(self.tree, wroot)
329 329 for fn, kind in entries:
330 330 wfn = join(wroot, fn)
331 331 self.updatestatus(wfn, self.getstat(wfn))
332 332 ds.pop(wfn, None)
333 333 wtopdir = topdir
334 334 if wtopdir and wtopdir[-1] != '/':
335 335 wtopdir += '/'
336 336 for wfn, state in ds.iteritems():
337 337 if not wfn.startswith(wtopdir):
338 338 continue
339 339 status = state[0]
340 340 st = self.getstat(wfn)
341 341 if status == 'r' and not st:
342 342 self.updatestatus(wfn, st, status=status)
343 343 else:
344 344 self.updatestatus(wfn, st, oldstatus=status)
345 345 self.check_deleted('!')
346 346 self.check_deleted('r')
347 347
348 348 def check_dirstate(self):
349 349 ds_info = self.dirstate_info()
350 350 if ds_info == self.ds_info:
351 351 return
352 352 self.ds_info = ds_info
353 353 if not self.ui.debugflag:
354 354 self.last_event = None
355 355 self.ui.note(_('%s dirstate reload\n') % self.event_time())
356 356 self.repo.dirstate.invalidate()
357 357 self.scan()
358 358 self.ui.note(_('%s end dirstate reload\n') % self.event_time())
359 359
360 360 def walk(self, states, tree, prefix=''):
361 361 # This is the "inner loop" when talking to the client.
362 362
363 363 for name, val in tree.iteritems():
364 364 path = join(prefix, name)
365 365 try:
366 366 if val in states:
367 367 yield path, val
368 368 except TypeError:
369 369 for p in self.walk(states, val, path):
370 370 yield p
371 371
372 372 def update_hgignore(self):
373 373 # An update of the ignore file can potentially change the
374 374 # states of all unknown and ignored files.
375 375
376 376 # XXX If the user has other ignore files outside the repo, or
377 377 # changes their list of ignore files at run time, we'll
378 378 # potentially never see changes to them. We could get the
379 379 # client to report to us what ignore data they're using.
380 380 # But it's easier to do nothing than to open that can of
381 381 # worms.
382 382
383 383 if self.repo.dirstate.ignorefunc is not None:
384 384 self.repo.dirstate.ignorefunc = None
385 385 self.ui.note('rescanning due to .hgignore change\n')
386 386 self.scan()
387 387
388 388 def getstat(self, wpath):
389 389 try:
390 390 return self.statcache[wpath]
391 391 except KeyError:
392 392 try:
393 393 return self.stat(wpath)
394 394 except OSError, err:
395 395 if err.errno != errno.ENOENT:
396 396 raise
397 397
398 398 def stat(self, wpath):
399 399 try:
400 400 st = os.lstat(join(self.wprefix, wpath))
401 401 ret = st.st_mode, st.st_size, st.st_mtime
402 402 self.statcache[wpath] = ret
403 403 return ret
404 404 except OSError, err:
405 405 self.statcache.pop(wpath, None)
406 406 raise
407 407
408 408 def created(self, wpath):
409 409 if wpath == '.hgignore':
410 410 self.update_hgignore()
411 411 try:
412 412 st = self.stat(wpath)
413 413 if stat.S_ISREG(st[0]):
414 414 self.updatestatus(wpath, st)
415 415 except OSError, err:
416 416 pass
417 417
418 418 def modified(self, wpath):
419 419 if wpath == '.hgignore':
420 420 self.update_hgignore()
421 421 try:
422 422 st = self.stat(wpath)
423 423 if stat.S_ISREG(st[0]):
424 424 if self.repo.dirstate[wpath] in 'lmn':
425 425 self.updatestatus(wpath, st)
426 426 except OSError:
427 427 pass
428 428
429 429 def deleted(self, wpath):
430 430 if wpath == '.hgignore':
431 431 self.update_hgignore()
432 432 elif wpath.startswith('.hg/'):
433 433 if wpath == '.hg/wlock':
434 434 self.check_dirstate()
435 435 return
436 436
437 437 self.updatestatus(wpath, None)
438 438
439 439 def schedule_work(self, wpath, evt):
440 440 self.eventq.setdefault(wpath, [])
441 441 prev = self.eventq[wpath]
442 442 try:
443 443 if prev and evt == 'm' and prev[-1] in 'cm':
444 444 return
445 445 self.eventq[wpath].append(evt)
446 446 finally:
447 447 self.deferred += 1
448 448 self.timeout = 250
449 449
450 450 def deferred_event(self, wpath, evt):
451 451 if evt == 'c':
452 452 self.created(wpath)
453 453 elif evt == 'm':
454 454 self.modified(wpath)
455 455 elif evt == 'd':
456 456 self.deleted(wpath)
457 457
458 458 def process_create(self, wpath, evt):
459 459 if self.ui.debugflag:
460 460 self.ui.note(_('%s event: created %s\n') %
461 461 (self.event_time(), wpath))
462 462
463 463 if evt.mask & inotify.IN_ISDIR:
464 464 self.scan(wpath)
465 465 else:
466 466 self.schedule_work(wpath, 'c')
467 467
468 468 def process_delete(self, wpath, evt):
469 469 if self.ui.debugflag:
470 470 self.ui.note(('%s event: deleted %s\n') %
471 471 (self.event_time(), wpath))
472 472
473 473 if evt.mask & inotify.IN_ISDIR:
474 474 self.scan(wpath)
475 475 else:
476 476 self.schedule_work(wpath, 'd')
477 477
478 478 def process_modify(self, wpath, evt):
479 479 if self.ui.debugflag:
480 480 self.ui.note(_('%s event: modified %s\n') %
481 481 (self.event_time(), wpath))
482 482
483 483 if not (evt.mask & inotify.IN_ISDIR):
484 484 self.schedule_work(wpath, 'm')
485 485
486 486 def process_unmount(self, evt):
487 487 self.ui.warn(_('filesystem containing %s was unmounted\n') %
488 488 evt.fullpath)
489 489 sys.exit(0)
490 490
491 491 def handle_event(self, fd, event):
492 492 if self.ui.debugflag:
493 493 self.ui.note('%s readable: %d bytes\n' %
494 494 (self.event_time(), self.threshold.readable()))
495 495 if not self.threshold():
496 496 if self.registered:
497 497 if self.ui.debugflag:
498 498 self.ui.note('%s below threshold - unhooking\n' %
499 499 (self.event_time()))
500 500 self.master.poll.unregister(fd)
501 501 self.registered = False
502 502 self.timeout = 250
503 503 else:
504 504 self.read_events()
505 505
506 506 def read_events(self, bufsize=None):
507 507 events = self.watcher.read(bufsize)
508 508 if self.ui.debugflag:
509 509 self.ui.note('%s reading %d events\n' %
510 510 (self.event_time(), len(events)))
511 511 for evt in events:
512 512 wpath = self.wpath(evt)
513 513 if evt.mask & inotify.IN_UNMOUNT:
514 514 self.process_unmount(wpath, evt)
515 515 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
516 516 self.process_modify(wpath, evt)
517 517 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
518 518 inotify.IN_MOVED_FROM):
519 519 self.process_delete(wpath, evt)
520 520 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
521 521 self.process_create(wpath, evt)
522 522
523 523 def handle_timeout(self):
524 524 if not self.registered:
525 525 if self.ui.debugflag:
526 526 self.ui.note('%s hooking back up with %d bytes readable\n' %
527 527 (self.event_time(), self.threshold.readable()))
528 528 self.read_events(0)
529 529 self.master.poll.register(self, select.POLLIN)
530 530 self.registered = True
531 531
532 532 if self.eventq:
533 533 if self.ui.debugflag:
534 534 self.ui.note('%s processing %d deferred events as %d\n' %
535 535 (self.event_time(), self.deferred,
536 536 len(self.eventq)))
537 537 for wpath, evts in util.sort(self.eventq.items()):
538 538 for evt in evts:
539 539 self.deferred_event(wpath, evt)
540 540 self.eventq.clear()
541 541 self.deferred = 0
542 542 self.timeout = None
543 543
544 544 def shutdown(self):
545 545 self.watcher.close()
546 546
547 547 class Server(object):
548 548 poll_events = select.POLLIN
549 549
550 550 def __init__(self, ui, repo, watcher, timeout):
551 551 self.ui = ui
552 552 self.repo = repo
553 553 self.watcher = watcher
554 554 self.timeout = timeout
555 555 self.sock = socket.socket(socket.AF_UNIX)
556 556 self.sockpath = self.repo.join('inotify.sock')
557 557 try:
558 558 self.sock.bind(self.sockpath)
559 559 except socket.error, err:
560 560 if err[0] == errno.EADDRINUSE:
561 561 raise AlreadyStartedException(_('could not start server: %s') \
562 562 % err[1])
563 563 raise
564 564 self.sock.listen(5)
565 565 self.fileno = self.sock.fileno
566 566
567 567 def handle_timeout(self):
568 568 pass
569 569
570 570 def handle_event(self, fd, event):
571 571 sock, addr = self.sock.accept()
572 572
573 573 cs = common.recvcs(sock)
574 574 version = ord(cs.read(1))
575 575
576 576 sock.sendall(chr(common.version))
577 577
578 578 if version != common.version:
579 579 self.ui.warn(_('received query from incompatible client '
580 580 'version %d\n') % version)
581 581 return
582 582
583 583 names = cs.read().split('\0')
584 584
585 585 states = names.pop()
586 586
587 587 self.ui.note(_('answering query for %r\n') % states)
588 588
589 589 if self.watcher.timeout:
590 590 # We got a query while a rescan is pending. Make sure we
591 591 # rescan before responding, or we could give back a wrong
592 592 # answer.
593 593 self.watcher.handle_timeout()
594 594
595 595 if not names:
596 596 def genresult(states, tree):
597 597 for fn, state in self.watcher.walk(states, tree):
598 598 yield fn
599 599 else:
600 600 def genresult(states, tree):
601 601 for fn in names:
602 602 l = self.watcher.lookup(fn, tree)
603 603 try:
604 604 if l in states:
605 605 yield fn
606 606 except TypeError:
607 607 for f, s in self.watcher.walk(states, l, fn):
608 608 yield f
609 609
610 610 results = ['\0'.join(r) for r in [
611 611 genresult('l', self.watcher.statustrees['l']),
612 612 genresult('m', self.watcher.statustrees['m']),
613 613 genresult('a', self.watcher.statustrees['a']),
614 614 genresult('r', self.watcher.statustrees['r']),
615 615 genresult('!', self.watcher.statustrees['!']),
616 616 '?' in states and genresult('?', self.watcher.statustrees['?']) or [],
617 617 [],
618 618 'c' in states and genresult('n', self.watcher.tree) or [],
619 619 ]]
620 620
621 621 try:
622 622 try:
623 623 sock.sendall(struct.pack(common.resphdrfmt,
624 624 *map(len, results)))
625 625 sock.sendall(''.join(results))
626 626 finally:
627 627 sock.shutdown(socket.SHUT_WR)
628 628 except socket.error, err:
629 629 if err[0] != errno.EPIPE:
630 630 raise
631 631
632 632 def shutdown(self):
633 633 self.sock.close()
634 634 try:
635 635 os.unlink(self.sockpath)
636 636 except OSError, err:
637 637 if err.errno != errno.ENOENT:
638 638 raise
639 639
640 640 class Master(object):
641 641 def __init__(self, ui, repo, timeout=None):
642 642 self.ui = ui
643 643 self.repo = repo
644 644 self.poll = select.poll()
645 645 self.watcher = Watcher(ui, repo, self)
646 646 self.server = Server(ui, repo, self.watcher, timeout)
647 647 self.table = {}
648 648 for obj in (self.watcher, self.server):
649 649 fd = obj.fileno()
650 650 self.table[fd] = obj
651 651 self.poll.register(fd, obj.poll_events)
652 652
653 653 def register(self, fd, mask):
654 654 self.poll.register(fd, mask)
655 655
656 656 def shutdown(self):
657 657 for obj in self.table.itervalues():
658 658 obj.shutdown()
659 659
660 660 def run(self):
661 661 self.watcher.setup()
662 662 self.ui.note(_('finished setup\n'))
663 663 if os.getenv('TIME_STARTUP'):
664 664 sys.exit(0)
665 665 while True:
666 666 timeout = None
667 667 timeobj = None
668 668 for obj in self.table.itervalues():
669 669 if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
670 670 timeout, timeobj = obj.timeout, obj
671 671 try:
672 672 if self.ui.debugflag:
673 673 if timeout is None:
674 674 self.ui.note('polling: no timeout\n')
675 675 else:
676 676 self.ui.note('polling: %sms timeout\n' % timeout)
677 677 events = self.poll.poll(timeout)
678 678 except select.error, err:
679 679 if err[0] == errno.EINTR:
680 680 continue
681 681 raise
682 682 if events:
683 683 for fd, event in events:
684 684 self.table[fd].handle_event(fd, event)
685 685 elif timeobj:
686 686 timeobj.handle_timeout()
687 687
688 688 def start(ui, repo):
689 689 m = Master(ui, repo)
690 690 sys.stdout.flush()
691 691 sys.stderr.flush()
692 692
693 693 pid = os.fork()
694 694 if pid:
695 695 return pid
696 696
697 697 os.setsid()
698 698
699 699 fd = os.open('/dev/null', os.O_RDONLY)
700 700 os.dup2(fd, 0)
701 701 if fd > 0:
702 702 os.close(fd)
703 703
704 704 fd = os.open(ui.config('inotify', 'log', '/dev/null'),
705 705 os.O_RDWR | os.O_CREAT | os.O_TRUNC)
706 706 os.dup2(fd, 1)
707 707 os.dup2(fd, 2)
708 708 if fd > 2:
709 709 os.close(fd)
710 710
711 711 try:
712 712 m.run()
713 713 finally:
714 714 m.shutdown()
715 715 os._exit(0)
General Comments 0
You need to be logged in to leave comments. Login now