##// END OF EJS Templates
inotify: make mask a class variable since it's instance-independant
Nicolas Dumazet -
r8383:dcfdcb51 default
parent child Browse files
Show More
@@ -1,759 +1,759 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 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 mask = (
120 inotify.IN_ATTRIB |
121 inotify.IN_CREATE |
122 inotify.IN_DELETE |
123 inotify.IN_DELETE_SELF |
124 inotify.IN_MODIFY |
125 inotify.IN_MOVED_FROM |
126 inotify.IN_MOVED_TO |
127 inotify.IN_MOVE_SELF |
128 inotify.IN_ONLYDIR |
129 inotify.IN_UNMOUNT |
130 0)
119 131
120 132 def __init__(self, ui, repo, master):
121 133 self.ui = ui
122 134 self.repo = repo
123 135 self.wprefix = self.repo.wjoin('')
124 136 self.timeout = None
125 137 self.master = master
126 self.mask = (
127 inotify.IN_ATTRIB |
128 inotify.IN_CREATE |
129 inotify.IN_DELETE |
130 inotify.IN_DELETE_SELF |
131 inotify.IN_MODIFY |
132 inotify.IN_MOVED_FROM |
133 inotify.IN_MOVED_TO |
134 inotify.IN_MOVE_SELF |
135 inotify.IN_ONLYDIR |
136 inotify.IN_UNMOUNT |
137 0)
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.repo.dirstate.__class__.inotifyserver = True
148 148
149 149 self.tree = {}
150 150 self.statcache = {}
151 151 self.statustrees = dict([(s, {}) for s in self.statuskeys])
152 152
153 153 self.watches = 0
154 154 self.last_event = None
155 155
156 156 self.eventq = {}
157 157 self.deferred = 0
158 158
159 159 self.ds_info = self.dirstate_info()
160 160 self.scan()
161 161
162 162 def event_time(self):
163 163 last = self.last_event
164 164 now = time.time()
165 165 self.last_event = now
166 166
167 167 if last is None:
168 168 return 'start'
169 169 delta = now - last
170 170 if delta < 5:
171 171 return '+%.3f' % delta
172 172 if delta < 50:
173 173 return '+%.2f' % delta
174 174 return '+%.1f' % delta
175 175
176 176 def dirstate_info(self):
177 177 try:
178 178 st = os.lstat(self.repo.join('dirstate'))
179 179 return st.st_mtime, st.st_ino
180 180 except OSError, err:
181 181 if err.errno != errno.ENOENT:
182 182 raise
183 183 return 0, 0
184 184
185 185 def add_watch(self, path, mask):
186 186 if not path:
187 187 return
188 188 if self.watcher.path(path) is None:
189 189 if self.ui.debugflag:
190 190 self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
191 191 try:
192 192 self.watcher.add(path, mask)
193 193 self.watches += 1
194 194 except OSError, err:
195 195 if err.errno in (errno.ENOENT, errno.ENOTDIR):
196 196 return
197 197 if err.errno != errno.ENOSPC:
198 198 raise
199 199 _explain_watch_limit(self.ui, self.repo, self.watches)
200 200
201 201 def setup(self):
202 202 self.ui.note(_('watching directories under %r\n') % self.repo.root)
203 203 self.add_watch(self.repo.path, inotify.IN_DELETE)
204 204 self.check_dirstate()
205 205
206 206 def wpath(self, evt):
207 207 path = evt.fullpath
208 208 if path == self.repo.root:
209 209 return ''
210 210 if path.startswith(self.wprefix):
211 211 return path[len(self.wprefix):]
212 212 raise 'wtf? ' + path
213 213
214 214 def dir(self, tree, path):
215 215 if path:
216 216 for name in path.split('/'):
217 217 tree.setdefault(name, {})
218 218 tree = tree[name]
219 219 return tree
220 220
221 221 def lookup(self, path, tree):
222 222 if path:
223 223 try:
224 224 for name in path.split('/'):
225 225 tree = tree[name]
226 226 except KeyError:
227 227 return 'x'
228 228 except TypeError:
229 229 return 'd'
230 230 return tree
231 231
232 232 def split(self, path):
233 233 c = path.rfind('/')
234 234 if c == -1:
235 235 return '', path
236 236 return path[:c], path[c+1:]
237 237
238 238 def filestatus(self, fn, st):
239 239 try:
240 240 type_, mode, size, time = self.repo.dirstate._map[fn][:4]
241 241 except KeyError:
242 242 type_ = '?'
243 243 if type_ == 'n':
244 244 st_mode, st_size, st_mtime = st
245 245 if size == -1:
246 246 return 'l'
247 247 if size and (size != st_size or (mode ^ st_mode) & 0100):
248 248 return 'm'
249 249 if time != int(st_mtime):
250 250 return 'l'
251 251 return 'n'
252 252 if type_ == '?' and self.repo.dirstate._ignore(fn):
253 253 return 'i'
254 254 return type_
255 255
256 256 def updatestatus(self, wfn, osstat=None, newstatus=None):
257 257 '''
258 258 Update the stored status of a file or directory.
259 259
260 260 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
261 261
262 262 newstatus: char in statuskeys, new status to apply.
263 263 '''
264 264 if osstat:
265 265 newstatus = self.filestatus(wfn, osstat)
266 266 else:
267 267 self.statcache.pop(wfn, None)
268 268 root, fn = self.split(wfn)
269 269 d = self.dir(self.tree, root)
270 270 oldstatus = d.get(fn)
271 271 isdir = False
272 272 if oldstatus:
273 273 try:
274 274 if not newstatus:
275 275 if oldstatus in 'almn':
276 276 newstatus = '!'
277 277 elif oldstatus == 'r':
278 278 newstatus = 'r'
279 279 except TypeError:
280 280 # oldstatus may be a dict left behind by a deleted
281 281 # directory
282 282 isdir = True
283 283 else:
284 284 if oldstatus in self.statuskeys and oldstatus != newstatus:
285 285 del self.dir(self.statustrees[oldstatus], root)[fn]
286 286 if self.ui.debugflag and oldstatus != newstatus:
287 287 if isdir:
288 288 self.ui.note(_('status: %r dir(%d) -> %s\n') %
289 289 (wfn, len(oldstatus), newstatus))
290 290 else:
291 291 self.ui.note(_('status: %r %s -> %s\n') %
292 292 (wfn, oldstatus, newstatus))
293 293 if not isdir:
294 294 if newstatus and newstatus != 'i':
295 295 d[fn] = newstatus
296 296 if newstatus in self.statuskeys:
297 297 dd = self.dir(self.statustrees[newstatus], root)
298 298 if oldstatus != newstatus or fn not in dd:
299 299 dd[fn] = newstatus
300 300 else:
301 301 d.pop(fn, None)
302 302 elif not newstatus:
303 303 # a directory is being removed, check its contents
304 304 for subfile, b in oldstatus.copy().iteritems():
305 305 self.updatestatus(wfn + '/' + subfile, None)
306 306
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, files 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 in files:
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 try:
340 340 st = self.stat(wfn)
341 341 except OSError:
342 342 status = state[0]
343 343 self.updatestatus(wfn, None, newstatus=status)
344 344 else:
345 345 self.updatestatus(wfn, st)
346 346 self.check_deleted('!')
347 347 self.check_deleted('r')
348 348
349 349 def check_dirstate(self):
350 350 ds_info = self.dirstate_info()
351 351 if ds_info == self.ds_info:
352 352 return
353 353 self.ds_info = ds_info
354 354 if not self.ui.debugflag:
355 355 self.last_event = None
356 356 self.ui.note(_('%s dirstate reload\n') % self.event_time())
357 357 self.repo.dirstate.invalidate()
358 358 self.scan()
359 359 self.ui.note(_('%s end dirstate reload\n') % self.event_time())
360 360
361 361 def walk(self, states, tree, prefix=''):
362 362 # This is the "inner loop" when talking to the client.
363 363
364 364 for name, val in tree.iteritems():
365 365 path = join(prefix, name)
366 366 try:
367 367 if val in states:
368 368 yield path, val
369 369 except TypeError:
370 370 for p in self.walk(states, val, path):
371 371 yield p
372 372
373 373 def update_hgignore(self):
374 374 # An update of the ignore file can potentially change the
375 375 # states of all unknown and ignored files.
376 376
377 377 # XXX If the user has other ignore files outside the repo, or
378 378 # changes their list of ignore files at run time, we'll
379 379 # potentially never see changes to them. We could get the
380 380 # client to report to us what ignore data they're using.
381 381 # But it's easier to do nothing than to open that can of
382 382 # worms.
383 383
384 384 if '_ignore' in self.repo.dirstate.__dict__:
385 385 delattr(self.repo.dirstate, '_ignore')
386 386 self.ui.note(_('rescanning due to .hgignore change\n'))
387 387 self.scan()
388 388
389 389 def getstat(self, wpath):
390 390 try:
391 391 return self.statcache[wpath]
392 392 except KeyError:
393 393 try:
394 394 return self.stat(wpath)
395 395 except OSError, err:
396 396 if err.errno != errno.ENOENT:
397 397 raise
398 398
399 399 def stat(self, wpath):
400 400 try:
401 401 st = os.lstat(join(self.wprefix, wpath))
402 402 ret = st.st_mode, st.st_size, st.st_mtime
403 403 self.statcache[wpath] = ret
404 404 return ret
405 405 except OSError:
406 406 self.statcache.pop(wpath, None)
407 407 raise
408 408
409 409 def created(self, wpath):
410 410 if wpath == '.hgignore':
411 411 self.update_hgignore()
412 412 try:
413 413 st = self.stat(wpath)
414 414 if stat.S_ISREG(st[0]):
415 415 self.updatestatus(wpath, st)
416 416 except OSError:
417 417 pass
418 418
419 419 def modified(self, wpath):
420 420 if wpath == '.hgignore':
421 421 self.update_hgignore()
422 422 try:
423 423 st = self.stat(wpath)
424 424 if stat.S_ISREG(st[0]):
425 425 if self.repo.dirstate[wpath] in 'lmn':
426 426 self.updatestatus(wpath, st)
427 427 except OSError:
428 428 pass
429 429
430 430 def deleted(self, wpath):
431 431 if wpath == '.hgignore':
432 432 self.update_hgignore()
433 433 elif wpath.startswith('.hg/'):
434 434 if wpath == '.hg/wlock':
435 435 self.check_dirstate()
436 436 return
437 437
438 438 self.updatestatus(wpath, None)
439 439
440 440 def schedule_work(self, wpath, evt):
441 441 self.eventq.setdefault(wpath, [])
442 442 prev = self.eventq[wpath]
443 443 try:
444 444 if prev and evt == 'm' and prev[-1] in 'cm':
445 445 return
446 446 self.eventq[wpath].append(evt)
447 447 finally:
448 448 self.deferred += 1
449 449 self.timeout = 250
450 450
451 451 def deferred_event(self, wpath, evt):
452 452 if evt == 'c':
453 453 self.created(wpath)
454 454 elif evt == 'm':
455 455 self.modified(wpath)
456 456 elif evt == 'd':
457 457 self.deleted(wpath)
458 458
459 459 def process_create(self, wpath, evt):
460 460 if self.ui.debugflag:
461 461 self.ui.note(_('%s event: created %s\n') %
462 462 (self.event_time(), wpath))
463 463
464 464 if evt.mask & inotify.IN_ISDIR:
465 465 self.scan(wpath)
466 466 else:
467 467 self.schedule_work(wpath, 'c')
468 468
469 469 def process_delete(self, wpath, evt):
470 470 if self.ui.debugflag:
471 471 self.ui.note(_('%s event: deleted %s\n') %
472 472 (self.event_time(), wpath))
473 473
474 474 if evt.mask & inotify.IN_ISDIR:
475 475 self.scan(wpath)
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 sorted(self.eventq.iteritems()):
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, repowatcher, timeout):
551 551 self.ui = ui
552 552 self.repo = repo
553 553 self.repowatcher = repowatcher
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 self.realsockpath = None
558 558 try:
559 559 self.sock.bind(self.sockpath)
560 560 except socket.error, err:
561 561 if err[0] == errno.EADDRINUSE:
562 562 raise AlreadyStartedException(_('could not start server: %s')
563 563 % err[1])
564 564 if err[0] == "AF_UNIX path too long":
565 565 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
566 566 self.realsockpath = os.path.join(tempdir, "inotify.sock")
567 567 try:
568 568 self.sock.bind(self.realsockpath)
569 569 os.symlink(self.realsockpath, self.sockpath)
570 570 except (OSError, socket.error), inst:
571 571 try:
572 572 os.unlink(self.realsockpath)
573 573 except:
574 574 pass
575 575 os.rmdir(tempdir)
576 576 if inst.errno == errno.EEXIST:
577 577 raise AlreadyStartedException(_('could not start server: %s')
578 578 % inst.strerror)
579 579 raise
580 580 else:
581 581 raise
582 582 self.sock.listen(5)
583 583 self.fileno = self.sock.fileno
584 584
585 585 def handle_timeout(self):
586 586 pass
587 587
588 588 def handle_event(self, fd, event):
589 589 sock, addr = self.sock.accept()
590 590
591 591 cs = common.recvcs(sock)
592 592 version = ord(cs.read(1))
593 593
594 594 sock.sendall(chr(common.version))
595 595
596 596 if version != common.version:
597 597 self.ui.warn(_('received query from incompatible client '
598 598 'version %d\n') % version)
599 599 return
600 600
601 601 names = cs.read().split('\0')
602 602
603 603 states = names.pop()
604 604
605 605 self.ui.note(_('answering query for %r\n') % states)
606 606
607 607 if self.repowatcher.timeout:
608 608 # We got a query while a rescan is pending. Make sure we
609 609 # rescan before responding, or we could give back a wrong
610 610 # answer.
611 611 self.repowatcher.handle_timeout()
612 612
613 613 if not names:
614 614 def genresult(states, tree):
615 615 for fn, state in self.repowatcher.walk(states, tree):
616 616 yield fn
617 617 else:
618 618 def genresult(states, tree):
619 619 for fn in names:
620 620 l = self.repowatcher.lookup(fn, tree)
621 621 try:
622 622 if l in states:
623 623 yield fn
624 624 except TypeError:
625 625 for f, s in self.repowatcher.walk(states, l, fn):
626 626 yield f
627 627
628 628 results = ['\0'.join(r) for r in [
629 629 genresult('l', self.repowatcher.statustrees['l']),
630 630 genresult('m', self.repowatcher.statustrees['m']),
631 631 genresult('a', self.repowatcher.statustrees['a']),
632 632 genresult('r', self.repowatcher.statustrees['r']),
633 633 genresult('!', self.repowatcher.statustrees['!']),
634 634 '?' in states
635 635 and genresult('?', self.repowatcher.statustrees['?'])
636 636 or [],
637 637 [],
638 638 'c' in states and genresult('n', self.repowatcher.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.repowatcher = RepoWatcher(ui, repo, self)
669 669 self.server = Server(ui, repo, self.repowatcher, timeout)
670 670 self.table = {}
671 671 for obj in (self.repowatcher, 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.repowatcher.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 def closefds(ignore):
713 713 # (from python bug #1177468)
714 714 # close all inherited file descriptors
715 715 # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
716 716 # a file descriptor is kept internally as os._urandomfd (created on demand
717 717 # the first time os.urandom() is called), and should not be closed
718 718 try:
719 719 os.urandom(4)
720 720 urandom_fd = getattr(os, '_urandomfd', None)
721 721 except AttributeError:
722 722 urandom_fd = None
723 723 ignore.append(urandom_fd)
724 724 for fd in range(3, 256):
725 725 if fd in ignore:
726 726 continue
727 727 try:
728 728 os.close(fd)
729 729 except OSError:
730 730 pass
731 731
732 732 m = Master(ui, repo)
733 733 sys.stdout.flush()
734 734 sys.stderr.flush()
735 735
736 736 pid = os.fork()
737 737 if pid:
738 738 return pid
739 739
740 740 closefds([m.server.fileno(), m.repowatcher.fileno()])
741 741 os.setsid()
742 742
743 743 fd = os.open('/dev/null', os.O_RDONLY)
744 744 os.dup2(fd, 0)
745 745 if fd > 0:
746 746 os.close(fd)
747 747
748 748 fd = os.open(ui.config('inotify', 'log', '/dev/null'),
749 749 os.O_RDWR | os.O_CREAT | os.O_TRUNC)
750 750 os.dup2(fd, 1)
751 751 os.dup2(fd, 2)
752 752 if fd > 2:
753 753 os.close(fd)
754 754
755 755 try:
756 756 m.run()
757 757 finally:
758 758 m.shutdown()
759 759 os._exit(0)
General Comments 0
You need to be logged in to leave comments. Login now