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