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