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