##// END OF EJS Templates
inotify: modular architecture for inotify clients...
Nicolas Dumazet -
r8551:7089d972 default
parent child Browse files
Show More
@@ -1,125 +1,127
1 # __init__.py - inotify-based status acceleration for Linux
1 # __init__.py - inotify-based status acceleration for Linux
2 #
2 #
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 '''inotify-based status acceleration for Linux systems
9 '''inotify-based status acceleration for Linux systems
10 '''
10 '''
11
11
12 # todo: socket permissions
12 # todo: socket permissions
13
13
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15 from mercurial import cmdutil, util
15 from mercurial import cmdutil, util
16 import client, errno, os, server, socket
16 import errno, os, server, socket
17 from weakref import proxy
17 from weakref import proxy
18 from client import client
18
19
19 def serve(ui, repo, **opts):
20 def serve(ui, repo, **opts):
20 '''start an inotify server for this repository'''
21 '''start an inotify server for this repository'''
21 timeout = opts.get('timeout')
22 timeout = opts.get('timeout')
22 if timeout:
23 if timeout:
23 timeout = float(timeout) * 1e3
24 timeout = float(timeout) * 1e3
24
25
25 class service:
26 class service:
26 def init(self):
27 def init(self):
27 try:
28 try:
28 self.master = server.master(ui, repo, timeout)
29 self.master = server.master(ui, repo, timeout)
29 except server.AlreadyStartedException, inst:
30 except server.AlreadyStartedException, inst:
30 raise util.Abort(str(inst))
31 raise util.Abort(str(inst))
31
32
32 def run(self):
33 def run(self):
33 try:
34 try:
34 self.master.run()
35 self.master.run()
35 finally:
36 finally:
36 self.master.shutdown()
37 self.master.shutdown()
37
38
38 service = service()
39 service = service()
39 cmdutil.service(opts, initfn=service.init, runfn=service.run)
40 cmdutil.service(opts, initfn=service.init, runfn=service.run)
40
41
41 def reposetup(ui, repo):
42 def reposetup(ui, repo):
42 if not hasattr(repo, 'dirstate'):
43 if not hasattr(repo, 'dirstate'):
43 return
44 return
44
45
45 # XXX: weakref until hg stops relying on __del__
46 # XXX: weakref until hg stops relying on __del__
46 repo = proxy(repo)
47 repo = proxy(repo)
47
48
48 class inotifydirstate(repo.dirstate.__class__):
49 class inotifydirstate(repo.dirstate.__class__):
49 # Set to True if we're the inotify server, so we don't attempt
50 # Set to True if we're the inotify server, so we don't attempt
50 # to recurse.
51 # to recurse.
51 inotifyserver = False
52 inotifyserver = False
52
53
53 def status(self, match, ignored, clean, unknown=True):
54 def status(self, match, ignored, clean, unknown=True):
54 files = match.files()
55 files = match.files()
55 if '.' in files:
56 if '.' in files:
56 files = []
57 files = []
58 cli = client(ui, repo)
57 try:
59 try:
58 if not ignored and not self.inotifyserver:
60 if not ignored and not self.inotifyserver:
59 result = client.query(ui, repo, files, match, False,
61 result = cli.statusquery(files, match, False,
60 clean, unknown)
62 clean, unknown)
61 if result and ui.config('inotify', 'debug'):
63 if result and ui.config('inotify', 'debug'):
62 r2 = super(inotifydirstate, self).status(
64 r2 = super(inotifydirstate, self).status(
63 match, False, clean, unknown)
65 match, False, clean, unknown)
64 for c,a,b in zip('LMARDUIC', result, r2):
66 for c,a,b in zip('LMARDUIC', result, r2):
65 for f in a:
67 for f in a:
66 if f not in b:
68 if f not in b:
67 ui.warn('*** inotify: %s +%s\n' % (c, f))
69 ui.warn('*** inotify: %s +%s\n' % (c, f))
68 for f in b:
70 for f in b:
69 if f not in a:
71 if f not in a:
70 ui.warn('*** inotify: %s -%s\n' % (c, f))
72 ui.warn('*** inotify: %s -%s\n' % (c, f))
71 result = r2
73 result = r2
72
74
73 if result is not None:
75 if result is not None:
74 return result
76 return result
75 except (OSError, socket.error), err:
77 except (OSError, socket.error), err:
76 autostart = ui.configbool('inotify', 'autostart', True)
78 autostart = ui.configbool('inotify', 'autostart', True)
77
79
78 if err[0] == errno.ECONNREFUSED:
80 if err[0] == errno.ECONNREFUSED:
79 ui.warn(_('(found dead inotify server socket; '
81 ui.warn(_('(found dead inotify server socket; '
80 'removing it)\n'))
82 'removing it)\n'))
81 os.unlink(repo.join('inotify.sock'))
83 os.unlink(repo.join('inotify.sock'))
82 if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart:
84 if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart:
83 ui.debug(_('(starting inotify server)\n'))
85 ui.debug(_('(starting inotify server)\n'))
84 try:
86 try:
85 try:
87 try:
86 server.start(ui, repo)
88 server.start(ui, repo)
87 except server.AlreadyStartedException, inst:
89 except server.AlreadyStartedException, inst:
88 # another process may have started its own
90 # another process may have started its own
89 # inotify server while this one was starting.
91 # inotify server while this one was starting.
90 ui.debug(str(inst))
92 ui.debug(str(inst))
91 except Exception, inst:
93 except Exception, inst:
92 ui.warn(_('could not start inotify server: '
94 ui.warn(_('could not start inotify server: '
93 '%s\n') % inst)
95 '%s\n') % inst)
94 else:
96 else:
95 # server is started, send query again
97 # server is started, send query again
96 try:
98 try:
97 return client.query(ui, repo, files, match,
99 return cli.statusquery(files, match, ignored,
98 ignored, clean, unknown)
100 clean, unknown)
99 except socket.error, err:
101 except socket.error, err:
100 ui.warn(_('could not talk to new inotify '
102 ui.warn(_('could not talk to new inotify '
101 'server: %s\n') % err[-1])
103 'server: %s\n') % err[-1])
102 elif err[0] in (errno.ECONNREFUSED, errno.ENOENT):
104 elif err[0] in (errno.ECONNREFUSED, errno.ENOENT):
103 # silently ignore normal errors if autostart is False
105 # silently ignore normal errors if autostart is False
104 ui.debug(_('(inotify server not running)\n'))
106 ui.debug(_('(inotify server not running)\n'))
105 else:
107 else:
106 ui.warn(_('failed to contact inotify server: %s\n')
108 ui.warn(_('failed to contact inotify server: %s\n')
107 % err[-1])
109 % err[-1])
108 ui.traceback()
110 ui.traceback()
109 # replace by old status function
111 # replace by old status function
110 self.status = super(inotifydirstate, self).status
112 self.status = super(inotifydirstate, self).status
111
113
112 return super(inotifydirstate, self).status(
114 return super(inotifydirstate, self).status(
113 match, ignored, clean, unknown)
115 match, ignored, clean, unknown)
114
116
115 repo.dirstate.__class__ = inotifydirstate
117 repo.dirstate.__class__ = inotifydirstate
116
118
117 cmdtable = {
119 cmdtable = {
118 '^inserve':
120 '^inserve':
119 (serve,
121 (serve,
120 [('d', 'daemon', None, _('run server in background')),
122 [('d', 'daemon', None, _('run server in background')),
121 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
123 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
122 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
124 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
123 ('', 'pid-file', '', _('name of file to write process ID to'))],
125 ('', 'pid-file', '', _('name of file to write process ID to'))],
124 _('hg inserve [OPT]...')),
126 _('hg inserve [OPT]...')),
125 }
127 }
@@ -1,65 +1,94
1 # client.py - inotify status client
1 # client.py - inotify status client
2 #
2 #
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
5 # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com>
5 #
6 #
6 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
8
9
9 from mercurial.i18n import _
10 from mercurial.i18n import _
10 import common
11 import common
11 import os, socket, struct
12 import os, socket, struct
12
13
13 def query(ui, repo, names, match, ignored, clean, unknown=True):
14 class client(object):
14 sock = socket.socket(socket.AF_UNIX)
15 def __init__(self, ui, repo):
15 sockpath = repo.join('inotify.sock')
16 self.ui = ui
16 try:
17 self.repo = repo
17 sock.connect(sockpath)
18 self.sock = socket.socket(socket.AF_UNIX)
18 except socket.error, err:
19
19 if err[0] == "AF_UNIX path too long":
20 def _connect(self):
20 sockpath = os.readlink(sockpath)
21 sockpath = self.repo.join('inotify.sock')
21 sock.connect(sockpath)
22 try:
22 else:
23 self.sock.connect(sockpath)
23 raise
24 except socket.error, err:
25 if err[0] == "AF_UNIX path too long":
26 sockpath = os.readlink(sockpath)
27 self.sock.connect(sockpath)
28 else:
29 raise
24
30
25 def genquery():
31 def _send(self, data):
26 for n in names:
32 """Sends protocol version number, and the data"""
27 yield n
33 self.sock.sendall(chr(common.version) + data)
28 states = 'almrx!'
34
29 if ignored:
35 self.sock.shutdown(socket.SHUT_WR)
30 raise ValueError('this is insanity')
31 if clean: states += 'c'
32 if unknown: states += '?'
33 yield states
34
36
35 req = '\0'.join(genquery())
37 def _receive(self):
38 """
39 Read data, check version number, extract headers,
40 and returns a tuple (data descriptor, header)
41 Returns (None, None) on error
42 """
43 cs = common.recvcs(self.sock)
44 version = ord(cs.read(1))
45 if version != common.version:
46 self.ui.warn(_('(inotify: received response from incompatible '
47 'server version %d)\n') % version)
48 return None, None
36
49
37 sock.sendall(chr(common.version))
50 # only one type of request is supported for now
38 sock.sendall(req)
51 type = 'STAT'
39 sock.shutdown(socket.SHUT_WR)
52 hdrfmt = common.resphdrfmts[type]
53 hdrsize = common.resphdrsizes[type]
54 try:
55 resphdr = struct.unpack(hdrfmt, cs.read(hdrsize))
56 except struct.error:
57 return None, None
40
58
41 cs = common.recvcs(sock)
59 return cs, resphdr
42 version = ord(cs.read(1))
60
61 def query(self, req):
62 self._connect()
43
63
44 if version != common.version:
64 self._send(req)
45 ui.warn(_('(inotify: received response from incompatible server '
65
46 'version %d)\n') % version)
66 return self._receive()
47 return None
67
68 def statusquery(self, names, match, ignored, clean, unknown=True):
48
69
49 # only one type of request is supported for now
70 def genquery():
50 type = 'STAT'
71 for n in names:
51 hdrfmt = common.resphdrfmts[type]
72 yield n
52 hdrsize = common.resphdrsizes[type]
73 states = 'almrx!'
53 try:
74 if ignored:
54 resphdr = struct.unpack(hdrfmt, cs.read(hdrsize))
75 raise ValueError('this is insanity')
55 except struct.error:
76 if clean: states += 'c'
56 return None
77 if unknown: states += '?'
78 yield states
79
80 req = '\0'.join(genquery())
57
81
58 def readnames(nbytes):
82 cs, resphdr = self.query(req)
59 if nbytes:
83
60 names = cs.read(nbytes)
84 if not cs:
61 if names:
85 return None
62 return filter(match, names.split('\0'))
63 return []
64
86
65 return map(readnames, resphdr)
87 def readnames(nbytes):
88 if nbytes:
89 names = cs.read(nbytes)
90 if names:
91 return filter(match, names.split('\0'))
92 return []
93
94 return map(readnames, resphdr)
General Comments 0
You need to be logged in to leave comments. Login now