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