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