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