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