##// END OF EJS Templates
inotify: follow new files if they are regular or a symlink....
Nicolas Dumazet -
r10089:8fab3172 default
parent child Browse files
Show More
@@ -1,429 +1,429 b''
1 # linuxserver.py - inotify status server for linux
1 # linuxserver.py - inotify status server for linux
2 #
2 #
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10 from mercurial import osutil, util
10 from mercurial import osutil, util
11 import common
11 import common
12 import server
12 import server
13 import errno, os, select, stat, sys, time
13 import errno, os, select, stat, sys, time
14
14
15 try:
15 try:
16 import linux as inotify
16 import linux as inotify
17 from linux import watcher
17 from linux import watcher
18 except ImportError:
18 except ImportError:
19 raise
19 raise
20
20
21 def walkrepodirs(dirstate, absroot):
21 def walkrepodirs(dirstate, absroot):
22 '''Iterate over all subdirectories of this repo.
22 '''Iterate over all subdirectories of this repo.
23 Exclude the .hg directory, any nested repos, and ignored dirs.'''
23 Exclude the .hg directory, any nested repos, and ignored dirs.'''
24 def walkit(dirname, top):
24 def walkit(dirname, top):
25 fullpath = server.join(absroot, dirname)
25 fullpath = server.join(absroot, dirname)
26 try:
26 try:
27 for name, kind in osutil.listdir(fullpath):
27 for name, kind in osutil.listdir(fullpath):
28 if kind == stat.S_IFDIR:
28 if kind == stat.S_IFDIR:
29 if name == '.hg':
29 if name == '.hg':
30 if not top:
30 if not top:
31 return
31 return
32 else:
32 else:
33 d = server.join(dirname, name)
33 d = server.join(dirname, name)
34 if dirstate._ignore(d):
34 if dirstate._ignore(d):
35 continue
35 continue
36 for subdir in walkit(d, False):
36 for subdir in walkit(d, False):
37 yield subdir
37 yield subdir
38 except OSError, err:
38 except OSError, err:
39 if err.errno not in server.walk_ignored_errors:
39 if err.errno not in server.walk_ignored_errors:
40 raise
40 raise
41 yield fullpath
41 yield fullpath
42
42
43 return walkit('', True)
43 return walkit('', True)
44
44
45 def _explain_watch_limit(ui, dirstate, rootabs):
45 def _explain_watch_limit(ui, dirstate, rootabs):
46 path = '/proc/sys/fs/inotify/max_user_watches'
46 path = '/proc/sys/fs/inotify/max_user_watches'
47 try:
47 try:
48 limit = int(file(path).read())
48 limit = int(file(path).read())
49 except IOError, err:
49 except IOError, err:
50 if err.errno != errno.ENOENT:
50 if err.errno != errno.ENOENT:
51 raise
51 raise
52 raise util.Abort(_('this system does not seem to '
52 raise util.Abort(_('this system does not seem to '
53 'support inotify'))
53 'support inotify'))
54 ui.warn(_('*** the current per-user limit on the number '
54 ui.warn(_('*** the current per-user limit on the number '
55 'of inotify watches is %s\n') % limit)
55 'of inotify watches is %s\n') % limit)
56 ui.warn(_('*** this limit is too low to watch every '
56 ui.warn(_('*** this limit is too low to watch every '
57 'directory in this repository\n'))
57 'directory in this repository\n'))
58 ui.warn(_('*** counting directories: '))
58 ui.warn(_('*** counting directories: '))
59 ndirs = len(list(walkrepodirs(dirstate, rootabs)))
59 ndirs = len(list(walkrepodirs(dirstate, rootabs)))
60 ui.warn(_('found %d\n') % ndirs)
60 ui.warn(_('found %d\n') % ndirs)
61 newlimit = min(limit, 1024)
61 newlimit = min(limit, 1024)
62 while newlimit < ((limit + ndirs) * 1.1):
62 while newlimit < ((limit + ndirs) * 1.1):
63 newlimit *= 2
63 newlimit *= 2
64 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
64 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
65 (limit, newlimit))
65 (limit, newlimit))
66 ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
66 ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
67 raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
67 raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
68 % rootabs)
68 % rootabs)
69
69
70 class pollable(object):
70 class pollable(object):
71 """
71 """
72 Interface to support polling.
72 Interface to support polling.
73 The file descriptor returned by fileno() is registered to a polling
73 The file descriptor returned by fileno() is registered to a polling
74 object.
74 object.
75 Usage:
75 Usage:
76 Every tick, check if an event has happened since the last tick:
76 Every tick, check if an event has happened since the last tick:
77 * If yes, call handle_events
77 * If yes, call handle_events
78 * If no, call handle_timeout
78 * If no, call handle_timeout
79 """
79 """
80 poll_events = select.POLLIN
80 poll_events = select.POLLIN
81 instances = {}
81 instances = {}
82 poll = select.poll()
82 poll = select.poll()
83
83
84 def fileno(self):
84 def fileno(self):
85 raise NotImplementedError
85 raise NotImplementedError
86
86
87 def handle_events(self, events):
87 def handle_events(self, events):
88 raise NotImplementedError
88 raise NotImplementedError
89
89
90 def handle_timeout(self):
90 def handle_timeout(self):
91 raise NotImplementedError
91 raise NotImplementedError
92
92
93 def shutdown(self):
93 def shutdown(self):
94 raise NotImplementedError
94 raise NotImplementedError
95
95
96 def register(self, timeout):
96 def register(self, timeout):
97 fd = self.fileno()
97 fd = self.fileno()
98
98
99 pollable.poll.register(fd, pollable.poll_events)
99 pollable.poll.register(fd, pollable.poll_events)
100 pollable.instances[fd] = self
100 pollable.instances[fd] = self
101
101
102 self.registered = True
102 self.registered = True
103 self.timeout = timeout
103 self.timeout = timeout
104
104
105 def unregister(self):
105 def unregister(self):
106 pollable.poll.unregister(self)
106 pollable.poll.unregister(self)
107 self.registered = False
107 self.registered = False
108
108
109 @classmethod
109 @classmethod
110 def run(cls):
110 def run(cls):
111 while True:
111 while True:
112 timeout = None
112 timeout = None
113 timeobj = None
113 timeobj = None
114 for obj in cls.instances.itervalues():
114 for obj in cls.instances.itervalues():
115 if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
115 if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
116 timeout, timeobj = obj.timeout, obj
116 timeout, timeobj = obj.timeout, obj
117 try:
117 try:
118 events = cls.poll.poll(timeout)
118 events = cls.poll.poll(timeout)
119 except select.error, err:
119 except select.error, err:
120 if err[0] == errno.EINTR:
120 if err[0] == errno.EINTR:
121 continue
121 continue
122 raise
122 raise
123 if events:
123 if events:
124 by_fd = {}
124 by_fd = {}
125 for fd, event in events:
125 for fd, event in events:
126 by_fd.setdefault(fd, []).append(event)
126 by_fd.setdefault(fd, []).append(event)
127
127
128 for fd, events in by_fd.iteritems():
128 for fd, events in by_fd.iteritems():
129 cls.instances[fd].handle_pollevents(events)
129 cls.instances[fd].handle_pollevents(events)
130
130
131 elif timeobj:
131 elif timeobj:
132 timeobj.handle_timeout()
132 timeobj.handle_timeout()
133
133
134 def eventaction(code):
134 def eventaction(code):
135 """
135 """
136 Decorator to help handle events in repowatcher
136 Decorator to help handle events in repowatcher
137 """
137 """
138 def decorator(f):
138 def decorator(f):
139 def wrapper(self, wpath):
139 def wrapper(self, wpath):
140 if code == 'm' and wpath in self.lastevent and \
140 if code == 'm' and wpath in self.lastevent and \
141 self.lastevent[wpath] in 'cm':
141 self.lastevent[wpath] in 'cm':
142 return
142 return
143 self.lastevent[wpath] = code
143 self.lastevent[wpath] = code
144 self.timeout = 250
144 self.timeout = 250
145
145
146 f(self, wpath)
146 f(self, wpath)
147
147
148 wrapper.func_name = f.func_name
148 wrapper.func_name = f.func_name
149 return wrapper
149 return wrapper
150 return decorator
150 return decorator
151
151
152 class repowatcher(server.repowatcher, pollable):
152 class repowatcher(server.repowatcher, pollable):
153 """
153 """
154 Watches inotify events
154 Watches inotify events
155 """
155 """
156 mask = (
156 mask = (
157 inotify.IN_ATTRIB |
157 inotify.IN_ATTRIB |
158 inotify.IN_CREATE |
158 inotify.IN_CREATE |
159 inotify.IN_DELETE |
159 inotify.IN_DELETE |
160 inotify.IN_DELETE_SELF |
160 inotify.IN_DELETE_SELF |
161 inotify.IN_MODIFY |
161 inotify.IN_MODIFY |
162 inotify.IN_MOVED_FROM |
162 inotify.IN_MOVED_FROM |
163 inotify.IN_MOVED_TO |
163 inotify.IN_MOVED_TO |
164 inotify.IN_MOVE_SELF |
164 inotify.IN_MOVE_SELF |
165 inotify.IN_ONLYDIR |
165 inotify.IN_ONLYDIR |
166 inotify.IN_UNMOUNT |
166 inotify.IN_UNMOUNT |
167 0)
167 0)
168
168
169 def __init__(self, ui, dirstate, root):
169 def __init__(self, ui, dirstate, root):
170 server.repowatcher.__init__(self, ui, dirstate, root)
170 server.repowatcher.__init__(self, ui, dirstate, root)
171
171
172 self.lastevent = {}
172 self.lastevent = {}
173 try:
173 try:
174 self.watcher = watcher.watcher()
174 self.watcher = watcher.watcher()
175 except OSError, err:
175 except OSError, err:
176 raise util.Abort(_('inotify service not available: %s') %
176 raise util.Abort(_('inotify service not available: %s') %
177 err.strerror)
177 err.strerror)
178 self.threshold = watcher.threshold(self.watcher)
178 self.threshold = watcher.threshold(self.watcher)
179 self.fileno = self.watcher.fileno
179 self.fileno = self.watcher.fileno
180 self.register(timeout=None)
180 self.register(timeout=None)
181
181
182 self.handle_timeout()
182 self.handle_timeout()
183 self.scan()
183 self.scan()
184
184
185 def event_time(self):
185 def event_time(self):
186 last = self.last_event
186 last = self.last_event
187 now = time.time()
187 now = time.time()
188 self.last_event = now
188 self.last_event = now
189
189
190 if last is None:
190 if last is None:
191 return 'start'
191 return 'start'
192 delta = now - last
192 delta = now - last
193 if delta < 5:
193 if delta < 5:
194 return '+%.3f' % delta
194 return '+%.3f' % delta
195 if delta < 50:
195 if delta < 50:
196 return '+%.2f' % delta
196 return '+%.2f' % delta
197 return '+%.1f' % delta
197 return '+%.1f' % delta
198
198
199 def add_watch(self, path, mask):
199 def add_watch(self, path, mask):
200 if not path:
200 if not path:
201 return
201 return
202 if self.watcher.path(path) is None:
202 if self.watcher.path(path) is None:
203 if self.ui.debugflag:
203 if self.ui.debugflag:
204 self.ui.note(_('watching %r\n') % path[self.prefixlen:])
204 self.ui.note(_('watching %r\n') % path[self.prefixlen:])
205 try:
205 try:
206 self.watcher.add(path, mask)
206 self.watcher.add(path, mask)
207 except OSError, err:
207 except OSError, err:
208 if err.errno in (errno.ENOENT, errno.ENOTDIR):
208 if err.errno in (errno.ENOENT, errno.ENOTDIR):
209 return
209 return
210 if err.errno != errno.ENOSPC:
210 if err.errno != errno.ENOSPC:
211 raise
211 raise
212 _explain_watch_limit(self.ui, self.dirstate, self.wprefix)
212 _explain_watch_limit(self.ui, self.dirstate, self.wprefix)
213
213
214 def setup(self):
214 def setup(self):
215 self.ui.note(_('watching directories under %r\n') % self.wprefix)
215 self.ui.note(_('watching directories under %r\n') % self.wprefix)
216 self.add_watch(self.wprefix + '.hg', inotify.IN_DELETE)
216 self.add_watch(self.wprefix + '.hg', inotify.IN_DELETE)
217 self.check_dirstate()
217 self.check_dirstate()
218
218
219 def scan(self, topdir=''):
219 def scan(self, topdir=''):
220 ds = self.dirstate._map.copy()
220 ds = self.dirstate._map.copy()
221 self.add_watch(server.join(self.wprefix, topdir), self.mask)
221 self.add_watch(server.join(self.wprefix, topdir), self.mask)
222 for root, dirs, files in server.walk(self.dirstate, self.wprefix,
222 for root, dirs, files in server.walk(self.dirstate, self.wprefix,
223 topdir):
223 topdir):
224 for d in dirs:
224 for d in dirs:
225 self.add_watch(server.join(root, d), self.mask)
225 self.add_watch(server.join(root, d), self.mask)
226 wroot = root[self.prefixlen:]
226 wroot = root[self.prefixlen:]
227 for fn in files:
227 for fn in files:
228 wfn = server.join(wroot, fn)
228 wfn = server.join(wroot, fn)
229 self.updatefile(wfn, self.getstat(wfn))
229 self.updatefile(wfn, self.getstat(wfn))
230 ds.pop(wfn, None)
230 ds.pop(wfn, None)
231 wtopdir = topdir
231 wtopdir = topdir
232 if wtopdir and wtopdir[-1] != '/':
232 if wtopdir and wtopdir[-1] != '/':
233 wtopdir += '/'
233 wtopdir += '/'
234 for wfn, state in ds.iteritems():
234 for wfn, state in ds.iteritems():
235 if not wfn.startswith(wtopdir):
235 if not wfn.startswith(wtopdir):
236 continue
236 continue
237 try:
237 try:
238 st = self.stat(wfn)
238 st = self.stat(wfn)
239 except OSError:
239 except OSError:
240 status = state[0]
240 status = state[0]
241 self.deletefile(wfn, status)
241 self.deletefile(wfn, status)
242 else:
242 else:
243 self.updatefile(wfn, st)
243 self.updatefile(wfn, st)
244 self.check_deleted('!')
244 self.check_deleted('!')
245 self.check_deleted('r')
245 self.check_deleted('r')
246
246
247 @eventaction('c')
247 @eventaction('c')
248 def created(self, wpath):
248 def created(self, wpath):
249 if wpath == '.hgignore':
249 if wpath == '.hgignore':
250 self.update_hgignore()
250 self.update_hgignore()
251 try:
251 try:
252 st = self.stat(wpath)
252 st = self.stat(wpath)
253 if stat.S_ISREG(st[0]):
253 if stat.S_ISREG(st[0]) or stat.S_ISLNK(st[0]):
254 self.updatefile(wpath, st)
254 self.updatefile(wpath, st)
255 except OSError:
255 except OSError:
256 pass
256 pass
257
257
258 @eventaction('m')
258 @eventaction('m')
259 def modified(self, wpath):
259 def modified(self, wpath):
260 if wpath == '.hgignore':
260 if wpath == '.hgignore':
261 self.update_hgignore()
261 self.update_hgignore()
262 try:
262 try:
263 st = self.stat(wpath)
263 st = self.stat(wpath)
264 if stat.S_ISREG(st[0]):
264 if stat.S_ISREG(st[0]):
265 if self.dirstate[wpath] in 'lmn':
265 if self.dirstate[wpath] in 'lmn':
266 self.updatefile(wpath, st)
266 self.updatefile(wpath, st)
267 except OSError:
267 except OSError:
268 pass
268 pass
269
269
270 @eventaction('d')
270 @eventaction('d')
271 def deleted(self, wpath):
271 def deleted(self, wpath):
272 if wpath == '.hgignore':
272 if wpath == '.hgignore':
273 self.update_hgignore()
273 self.update_hgignore()
274 elif wpath.startswith('.hg/'):
274 elif wpath.startswith('.hg/'):
275 if wpath == '.hg/wlock':
275 if wpath == '.hg/wlock':
276 self.check_dirstate()
276 self.check_dirstate()
277 return
277 return
278
278
279 self.deletefile(wpath, self.dirstate[wpath])
279 self.deletefile(wpath, self.dirstate[wpath])
280
280
281 def process_create(self, wpath, evt):
281 def process_create(self, wpath, evt):
282 if self.ui.debugflag:
282 if self.ui.debugflag:
283 self.ui.note(_('%s event: created %s\n') %
283 self.ui.note(_('%s event: created %s\n') %
284 (self.event_time(), wpath))
284 (self.event_time(), wpath))
285
285
286 if evt.mask & inotify.IN_ISDIR:
286 if evt.mask & inotify.IN_ISDIR:
287 self.scan(wpath)
287 self.scan(wpath)
288 else:
288 else:
289 self.created(wpath)
289 self.created(wpath)
290
290
291 def process_delete(self, wpath, evt):
291 def process_delete(self, wpath, evt):
292 if self.ui.debugflag:
292 if self.ui.debugflag:
293 self.ui.note(_('%s event: deleted %s\n') %
293 self.ui.note(_('%s event: deleted %s\n') %
294 (self.event_time(), wpath))
294 (self.event_time(), wpath))
295
295
296 if evt.mask & inotify.IN_ISDIR:
296 if evt.mask & inotify.IN_ISDIR:
297 tree = self.tree.dir(wpath)
297 tree = self.tree.dir(wpath)
298 todelete = [wfn for wfn, ignore in tree.walk('?')]
298 todelete = [wfn for wfn, ignore in tree.walk('?')]
299 for fn in todelete:
299 for fn in todelete:
300 self.deletefile(fn, '?')
300 self.deletefile(fn, '?')
301 self.scan(wpath)
301 self.scan(wpath)
302 else:
302 else:
303 self.deleted(wpath)
303 self.deleted(wpath)
304
304
305 def process_modify(self, wpath, evt):
305 def process_modify(self, wpath, evt):
306 if self.ui.debugflag:
306 if self.ui.debugflag:
307 self.ui.note(_('%s event: modified %s\n') %
307 self.ui.note(_('%s event: modified %s\n') %
308 (self.event_time(), wpath))
308 (self.event_time(), wpath))
309
309
310 if not (evt.mask & inotify.IN_ISDIR):
310 if not (evt.mask & inotify.IN_ISDIR):
311 self.modified(wpath)
311 self.modified(wpath)
312
312
313 def process_unmount(self, evt):
313 def process_unmount(self, evt):
314 self.ui.warn(_('filesystem containing %s was unmounted\n') %
314 self.ui.warn(_('filesystem containing %s was unmounted\n') %
315 evt.fullpath)
315 evt.fullpath)
316 sys.exit(0)
316 sys.exit(0)
317
317
318 def handle_pollevents(self, events):
318 def handle_pollevents(self, events):
319 if self.ui.debugflag:
319 if self.ui.debugflag:
320 self.ui.note(_('%s readable: %d bytes\n') %
320 self.ui.note(_('%s readable: %d bytes\n') %
321 (self.event_time(), self.threshold.readable()))
321 (self.event_time(), self.threshold.readable()))
322 if not self.threshold():
322 if not self.threshold():
323 if self.registered:
323 if self.registered:
324 if self.ui.debugflag:
324 if self.ui.debugflag:
325 self.ui.note(_('%s below threshold - unhooking\n') %
325 self.ui.note(_('%s below threshold - unhooking\n') %
326 (self.event_time()))
326 (self.event_time()))
327 self.unregister()
327 self.unregister()
328 self.timeout = 250
328 self.timeout = 250
329 else:
329 else:
330 self.read_events()
330 self.read_events()
331
331
332 def read_events(self, bufsize=None):
332 def read_events(self, bufsize=None):
333 events = self.watcher.read(bufsize)
333 events = self.watcher.read(bufsize)
334 if self.ui.debugflag:
334 if self.ui.debugflag:
335 self.ui.note(_('%s reading %d events\n') %
335 self.ui.note(_('%s reading %d events\n') %
336 (self.event_time(), len(events)))
336 (self.event_time(), len(events)))
337 for evt in events:
337 for evt in events:
338 assert evt.fullpath.startswith(self.wprefix)
338 assert evt.fullpath.startswith(self.wprefix)
339 wpath = evt.fullpath[self.prefixlen:]
339 wpath = evt.fullpath[self.prefixlen:]
340
340
341 # paths have been normalized, wpath never ends with a '/'
341 # paths have been normalized, wpath never ends with a '/'
342
342
343 if wpath.startswith('.hg/') and evt.mask & inotify.IN_ISDIR:
343 if wpath.startswith('.hg/') and evt.mask & inotify.IN_ISDIR:
344 # ignore subdirectories of .hg/ (merge, patches...)
344 # ignore subdirectories of .hg/ (merge, patches...)
345 continue
345 continue
346
346
347 if evt.mask & inotify.IN_UNMOUNT:
347 if evt.mask & inotify.IN_UNMOUNT:
348 self.process_unmount(wpath, evt)
348 self.process_unmount(wpath, evt)
349 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
349 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
350 self.process_modify(wpath, evt)
350 self.process_modify(wpath, evt)
351 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
351 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
352 inotify.IN_MOVED_FROM):
352 inotify.IN_MOVED_FROM):
353 self.process_delete(wpath, evt)
353 self.process_delete(wpath, evt)
354 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
354 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
355 self.process_create(wpath, evt)
355 self.process_create(wpath, evt)
356
356
357 self.lastevent.clear()
357 self.lastevent.clear()
358
358
359 def handle_timeout(self):
359 def handle_timeout(self):
360 if not self.registered:
360 if not self.registered:
361 if self.ui.debugflag:
361 if self.ui.debugflag:
362 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
362 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
363 (self.event_time(), self.threshold.readable()))
363 (self.event_time(), self.threshold.readable()))
364 self.read_events(0)
364 self.read_events(0)
365 self.register(timeout=None)
365 self.register(timeout=None)
366
366
367 self.timeout = None
367 self.timeout = None
368
368
369 def shutdown(self):
369 def shutdown(self):
370 self.watcher.close()
370 self.watcher.close()
371
371
372 def debug(self):
372 def debug(self):
373 """
373 """
374 Returns a sorted list of relatives paths currently watched,
374 Returns a sorted list of relatives paths currently watched,
375 for debugging purposes.
375 for debugging purposes.
376 """
376 """
377 return sorted(tuple[0][self.prefixlen:] for tuple in self.watcher)
377 return sorted(tuple[0][self.prefixlen:] for tuple in self.watcher)
378
378
379 class socketlistener(server.socketlistener, pollable):
379 class socketlistener(server.socketlistener, pollable):
380 """
380 """
381 Listens for client queries on unix socket inotify.sock
381 Listens for client queries on unix socket inotify.sock
382 """
382 """
383 def __init__(self, ui, root, repowatcher, timeout):
383 def __init__(self, ui, root, repowatcher, timeout):
384 server.socketlistener.__init__(self, ui, root, repowatcher, timeout)
384 server.socketlistener.__init__(self, ui, root, repowatcher, timeout)
385 self.register(timeout=timeout)
385 self.register(timeout=timeout)
386
386
387 def handle_timeout(self):
387 def handle_timeout(self):
388 pass
388 pass
389
389
390 def handle_pollevents(self, events):
390 def handle_pollevents(self, events):
391 for e in events:
391 for e in events:
392 self.accept_connection()
392 self.accept_connection()
393
393
394 def shutdown(self):
394 def shutdown(self):
395 self.sock.close()
395 self.sock.close()
396 try:
396 try:
397 os.unlink(self.sockpath)
397 os.unlink(self.sockpath)
398 if self.realsockpath:
398 if self.realsockpath:
399 os.unlink(self.realsockpath)
399 os.unlink(self.realsockpath)
400 os.rmdir(os.path.dirname(self.realsockpath))
400 os.rmdir(os.path.dirname(self.realsockpath))
401 except OSError, err:
401 except OSError, err:
402 if err.errno != errno.ENOENT:
402 if err.errno != errno.ENOENT:
403 raise
403 raise
404
404
405 def answer_stat_query(self, cs):
405 def answer_stat_query(self, cs):
406 if self.repowatcher.timeout:
406 if self.repowatcher.timeout:
407 # We got a query while a rescan is pending. Make sure we
407 # We got a query while a rescan is pending. Make sure we
408 # rescan before responding, or we could give back a wrong
408 # rescan before responding, or we could give back a wrong
409 # answer.
409 # answer.
410 self.repowatcher.handle_timeout()
410 self.repowatcher.handle_timeout()
411 return server.socketlistener.answer_stat_query(self, cs)
411 return server.socketlistener.answer_stat_query(self, cs)
412
412
413 class master(object):
413 class master(object):
414 def __init__(self, ui, dirstate, root, timeout=None):
414 def __init__(self, ui, dirstate, root, timeout=None):
415 self.ui = ui
415 self.ui = ui
416 self.repowatcher = repowatcher(ui, dirstate, root)
416 self.repowatcher = repowatcher(ui, dirstate, root)
417 self.socketlistener = socketlistener(ui, root, self.repowatcher,
417 self.socketlistener = socketlistener(ui, root, self.repowatcher,
418 timeout)
418 timeout)
419
419
420 def shutdown(self):
420 def shutdown(self):
421 for obj in pollable.instances.itervalues():
421 for obj in pollable.instances.itervalues():
422 obj.shutdown()
422 obj.shutdown()
423
423
424 def run(self):
424 def run(self):
425 self.repowatcher.setup()
425 self.repowatcher.setup()
426 self.ui.note(_('finished setup\n'))
426 self.ui.note(_('finished setup\n'))
427 if os.getenv('TIME_STARTUP'):
427 if os.getenv('TIME_STARTUP'):
428 sys.exit(0)
428 sys.exit(0)
429 pollable.run()
429 pollable.run()
General Comments 0
You need to be logged in to leave comments. Login now