##// END OF EJS Templates
inotify: server: move split() out of server...
inotify: server: move split() out of server split() has nothing to do with the server logic, it does not need to be in the class. Move it on top, next to join() which does the opposite.

File last commit:

r8787:9aca7650 default
r8787:9aca7650 default
Show More
server.py
818 lines | 26.1 KiB | text/x-python | PythonLexer
Bryan O'Sullivan
Add inotify extension
r6239 # server.py - inotify status server
#
# Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
# Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
Bryan O'Sullivan
Add inotify extension
r6239
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 from mercurial.i18n import _
Brendan Cully
inotify: remove unused imports (thanks pyflakes)
r7420 from mercurial import osutil, util
Bryan O'Sullivan
Add inotify extension
r6239 import common
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 import errno, os, select, socket, stat, struct, sys, tempfile, time
Bryan O'Sullivan
Add inotify extension
r6239
try:
Brendan Cully
Use relative imports in inotify.server....
r6994 import linux as inotify
from linux import watcher
Bryan O'Sullivan
Add inotify extension
r6239 except ImportError:
raise
class AlreadyStartedException(Exception): pass
def join(a, b):
if a:
if a[-1] == '/':
return a + b
return a + '/' + b
return b
Nicolas Dumazet
inotify: server: move split() out of server...
r8787 def split(path):
c = path.rfind('/')
if c == -1:
return '', path
return path[:c], path[c+1:]
Bryan O'Sullivan
Add inotify extension
r6239 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
def walkrepodirs(repo):
'''Iterate over all subdirectories of this repo.
Exclude the .hg directory, any nested repos, and ignored dirs.'''
rootslash = repo.root + os.sep
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify walking...
r8322
Bryan O'Sullivan
Add inotify extension
r6239 def walkit(dirname, top):
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify...
r8321 fullpath = rootslash + dirname
Bryan O'Sullivan
Add inotify extension
r6239 try:
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify...
r8321 for name, kind in osutil.listdir(fullpath):
Bryan O'Sullivan
Add inotify extension
r6239 if kind == stat.S_IFDIR:
if name == '.hg':
Nicolas Dumazet
inotify: inotify.server.walk*() cleanup...
r8323 if not top:
return
Bryan O'Sullivan
Add inotify extension
r6239 else:
d = join(dirname, name)
if repo.dirstate._ignore(d):
continue
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify walking...
r8322 for subdir in walkit(d, False):
yield subdir
Bryan O'Sullivan
Add inotify extension
r6239 except OSError, err:
if err.errno not in walk_ignored_errors:
raise
Nicolas Dumazet
inotify: inotify.server.walk*() remove unnecessary var...
r8324 yield fullpath
Nicolas Dumazet
inotify: inotify.server.walkrepodirs() simplify walking...
r8322
return walkit('', True)
Bryan O'Sullivan
Add inotify extension
r6239
def walk(repo, root):
'''Like os.walk, but only yields regular files.'''
# This function is critical to performance during startup.
rootslash = repo.root + os.sep
def walkit(root, reporoot):
files, dirs = [], []
try:
fullpath = rootslash + root
for name, kind in osutil.listdir(fullpath):
if kind == stat.S_IFDIR:
if name == '.hg':
Nicolas Dumazet
inotify: inotify.server.walk() simplify control flow
r8325 if not reporoot:
Nicolas Dumazet
inotify: inotify.server.walk*() cleanup...
r8323 return
Nicolas Dumazet
inotify: inotify.server.walk() simplify control flow
r8325 else:
dirs.append(name)
Nicolas Dumazet
inotify: server.walk(): use yield instead of for...
r8381 path = join(root, name)
if repo.dirstate._ignore(path):
continue
for result in walkit(path, False):
yield result
Bryan O'Sullivan
Add inotify extension
r6239 elif kind in (stat.S_IFREG, stat.S_IFLNK):
Nicolas Dumazet
inotify: inotify.server.walk() filetype is never used, do not yield it
r8334 files.append(name)
Nicolas Dumazet
inotify: inotify.server.walk*() remove unnecessary var...
r8324 yield fullpath, dirs, files
Bryan O'Sullivan
Add inotify extension
r6239
except OSError, err:
if err.errno not in walk_ignored_errors:
raise
Nicolas Dumazet
inotify: inotify.server.walk() simplify algorithm...
r8320
return walkit(root, root == '')
Bryan O'Sullivan
Add inotify extension
r6239
Benoit Boissinot
inotify: remove unused variables
r8786 def _explain_watch_limit(ui, repo):
Bryan O'Sullivan
Add inotify extension
r6239 path = '/proc/sys/fs/inotify/max_user_watches'
try:
limit = int(file(path).read())
except IOError, err:
if err.errno != errno.ENOENT:
raise
raise util.Abort(_('this system does not seem to '
'support inotify'))
ui.warn(_('*** the current per-user limit on the number '
'of inotify watches is %s\n') % limit)
ui.warn(_('*** this limit is too low to watch every '
'directory in this repository\n'))
ui.warn(_('*** counting directories: '))
ndirs = len(list(walkrepodirs(repo)))
ui.warn(_('found %d\n') % ndirs)
newlimit = min(limit, 1024)
while newlimit < ((limit + ndirs) * 1.1):
newlimit *= 2
ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') %
(limit, newlimit))
ui.warn(_('*** echo %d > %s\n') % (newlimit, path))
raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
% repo.root)
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 class pollable(object):
"""
Interface to support polling.
The file descriptor returned by fileno() is registered to a polling
object.
Usage:
Every tick, check if an event has happened since the last tick:
* If yes, call handle_events
* If no, call handle_timeout
"""
Bryan O'Sullivan
Add inotify extension
r6239 poll_events = select.POLLIN
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 def fileno(self):
raise NotImplementedError
def handle_events(self, events):
raise NotImplementedError
def handle_timeout(self):
raise NotImplementedError
def shutdown(self):
raise NotImplementedError
class repowatcher(pollable):
"""
Watches inotify events
"""
Bryan O'Sullivan
Add inotify extension
r6239 statuskeys = 'almr!?'
Nicolas Dumazet
inotify: make mask a class variable since it's instance-independant
r8383 mask = (
inotify.IN_ATTRIB |
inotify.IN_CREATE |
inotify.IN_DELETE |
inotify.IN_DELETE_SELF |
inotify.IN_MODIFY |
inotify.IN_MOVED_FROM |
inotify.IN_MOVED_TO |
inotify.IN_MOVE_SELF |
inotify.IN_ONLYDIR |
inotify.IN_UNMOUNT |
0)
Bryan O'Sullivan
Add inotify extension
r6239
def __init__(self, ui, repo, master):
self.ui = ui
self.repo = repo
self.wprefix = self.repo.wjoin('')
self.timeout = None
self.master = master
try:
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 self.watcher = watcher.watcher()
Bryan O'Sullivan
Add inotify extension
r6239 except OSError, err:
raise util.Abort(_('inotify service not available: %s') %
err.strerror)
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 self.threshold = watcher.threshold(self.watcher)
Bryan O'Sullivan
Add inotify extension
r6239 self.registered = True
self.fileno = self.watcher.fileno
self.tree = {}
self.statcache = {}
self.statustrees = dict([(s, {}) for s in self.statuskeys])
self.watches = 0
self.last_event = None
Nicolas Dumazet
inotify: do not defer inotify events processing...
r8605 self.lastevent = {}
Bryan O'Sullivan
Add inotify extension
r6239
self.ds_info = self.dirstate_info()
Nicolas Dumazet
inotify: do not recurse in handle_timeout(): call it explicitely, not in scan()...
r8604 self.handle_timeout()
Bryan O'Sullivan
Add inotify extension
r6239 self.scan()
def event_time(self):
last = self.last_event
now = time.time()
self.last_event = now
if last is None:
return 'start'
delta = now - last
if delta < 5:
return '+%.3f' % delta
if delta < 50:
return '+%.2f' % delta
return '+%.1f' % delta
def dirstate_info(self):
try:
st = os.lstat(self.repo.join('dirstate'))
return st.st_mtime, st.st_ino
except OSError, err:
if err.errno != errno.ENOENT:
raise
return 0, 0
def add_watch(self, path, mask):
if not path:
return
if self.watcher.path(path) is None:
if self.ui.debugflag:
self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
try:
self.watcher.add(path, mask)
self.watches += 1
except OSError, err:
if err.errno in (errno.ENOENT, errno.ENOTDIR):
return
if err.errno != errno.ENOSPC:
raise
Benoit Boissinot
inotify: remove unused variables
r8786 _explain_watch_limit(self.ui, self.repo)
Bryan O'Sullivan
Add inotify extension
r6239
def setup(self):
self.ui.note(_('watching directories under %r\n') % self.repo.root)
self.add_watch(self.repo.path, inotify.IN_DELETE)
self.check_dirstate()
def wpath(self, evt):
path = evt.fullpath
if path == self.repo.root:
return ''
if path.startswith(self.wprefix):
return path[len(self.wprefix):]
raise 'wtf? ' + path
def dir(self, tree, path):
if path:
for name in path.split('/'):
Nicolas Dumazet
inotify: dic.setdefault(k, d) ; v = dic[k] --> v = dic.setdefault(k, d)...
r8384 tree = tree.setdefault(name, {})
Bryan O'Sullivan
Add inotify extension
r6239 return tree
def lookup(self, path, tree):
if path:
try:
for name in path.split('/'):
tree = tree[name]
except KeyError:
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 return 'x'
Bryan O'Sullivan
Add inotify extension
r6239 except TypeError:
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 return 'd'
Bryan O'Sullivan
Add inotify extension
r6239 return tree
def filestatus(self, fn, st):
try:
type_, mode, size, time = self.repo.dirstate._map[fn][:4]
except KeyError:
type_ = '?'
if type_ == 'n':
st_mode, st_size, st_mtime = st
Matt Mackall
inotify: fix confusion on files in lookup state
r7082 if size == -1:
return 'l'
Bryan O'Sullivan
Add inotify extension
r6239 if size and (size != st_size or (mode ^ st_mode) & 0100):
return 'm'
if time != int(st_mtime):
return 'l'
return 'n'
if type_ == '?' and self.repo.dirstate._ignore(fn):
return 'i'
return type_
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 def updatefile(self, wfn, osstat):
'''
update the file entry of an existing file.
osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
'''
self._updatestatus(wfn, self.filestatus(wfn, osstat))
def deletefile(self, wfn, oldstatus):
'''
update the entry of a file which has been deleted.
oldstatus: char in statuskeys, status of the file before deletion
'''
if oldstatus == 'r':
newstatus = 'r'
elif oldstatus in 'almn':
newstatus = '!'
else:
newstatus = None
self.statcache.pop(wfn, None)
self._updatestatus(wfn, newstatus)
def _updatestatus(self, wfn, newstatus):
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 '''
Update the stored status of a file or directory.
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 newstatus: - char in (statuskeys + 'ni'), new status to apply.
- or None, to stop tracking wfn
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 '''
Nicolas Dumazet
inotify: server: move split() out of server...
r8787 root, fn = split(wfn)
Bryan O'Sullivan
Add inotify extension
r6239 d = self.dir(self.tree, root)
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 oldstatus = d.get(fn)
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 # oldstatus can be either:
# - None : fn is new
# - a char in statuskeys: fn is a (tracked) file
# - a dict: fn is a directory
isdir = isinstance(oldstatus, dict)
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 if self.ui.debugflag and oldstatus != newstatus:
Bryan O'Sullivan
Add inotify extension
r6239 if isdir:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('status: %r dir(%d) -> %s\n') %
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 (wfn, len(oldstatus), newstatus))
Bryan O'Sullivan
Add inotify extension
r6239 else:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('status: %r %s -> %s\n') %
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 (wfn, oldstatus, newstatus))
Bryan O'Sullivan
Add inotify extension
r6239 if not isdir:
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 if oldstatus and oldstatus in self.statuskeys \
and oldstatus != newstatus:
del self.dir(self.statustrees[oldstatus], root)[fn]
Nicolas Dumazet
inotify: RepoWatcher.updatestatus: document & use meaningful parameter names
r8382 if newstatus and newstatus != 'i':
d[fn] = newstatus
if newstatus in self.statuskeys:
dd = self.dir(self.statustrees[newstatus], root)
if oldstatus != newstatus or fn not in dd:
dd[fn] = newstatus
Bryan O'Sullivan
Add inotify extension
r6239 else:
d.pop(fn, None)
Nicolas Dumazet
Fixing issue1542, adding a relevant test...
r7892
Bryan O'Sullivan
Add inotify extension
r6239
def check_deleted(self, key):
# Files that had been deleted but were present in the dirstate
# may have vanished from the dirstate; we must clean them up.
nuke = []
for wfn, ignore in self.walk(key, self.statustrees[key]):
if wfn not in self.repo.dirstate:
nuke.append(wfn)
for wfn in nuke:
Nicolas Dumazet
inotify: server: move split() out of server...
r8787 root, fn = split(wfn)
Bryan O'Sullivan
Add inotify extension
r6239 del self.dir(self.statustrees[key], root)[fn]
del self.dir(self.tree, root)[fn]
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def scan(self, topdir=''):
ds = self.repo.dirstate._map.copy()
self.add_watch(join(self.repo.root, topdir), self.mask)
Nicolas Dumazet
inotify: inotify.server.walk() filetype is never used, do not yield it
r8334 for root, dirs, files in walk(self.repo, topdir):
Bryan O'Sullivan
Add inotify extension
r6239 for d in dirs:
self.add_watch(join(root, d), self.mask)
wroot = root[len(self.wprefix):]
Nicolas Dumazet
inotify: inotify.server.walk() filetype is never used, do not yield it
r8334 for fn in files:
Bryan O'Sullivan
Add inotify extension
r6239 wfn = join(wroot, fn)
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.updatefile(wfn, self.getstat(wfn))
Bryan O'Sullivan
Add inotify extension
r6239 ds.pop(wfn, None)
wtopdir = topdir
if wtopdir and wtopdir[-1] != '/':
wtopdir += '/'
for wfn, state in ds.iteritems():
if not wfn.startswith(wtopdir):
continue
Gerard Korsten
inotify: server raising an error when removing a file (issue1371)...
r7302 try:
st = self.stat(wfn)
except OSError:
status = state[0]
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.deletefile(wfn, status)
Bryan O'Sullivan
Add inotify extension
r6239 else:
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.updatefile(wfn, st)
Bryan O'Sullivan
Add inotify extension
r6239 self.check_deleted('!')
self.check_deleted('r')
def check_dirstate(self):
ds_info = self.dirstate_info()
if ds_info == self.ds_info:
return
self.ds_info = ds_info
if not self.ui.debugflag:
self.last_event = None
self.ui.note(_('%s dirstate reload\n') % self.event_time())
self.repo.dirstate.invalidate()
Nicolas Dumazet
inotify: do not recurse in handle_timeout(): call it explicitely, not in scan()...
r8604 self.handle_timeout()
Bryan O'Sullivan
Add inotify extension
r6239 self.scan()
self.ui.note(_('%s end dirstate reload\n') % self.event_time())
def walk(self, states, tree, prefix=''):
# This is the "inner loop" when talking to the client.
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 for name, val in tree.iteritems():
path = join(prefix, name)
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 try:
if val in states:
yield path, val
except TypeError:
for p in self.walk(states, val, path):
yield p
Bryan O'Sullivan
Add inotify extension
r6239
def update_hgignore(self):
# An update of the ignore file can potentially change the
# states of all unknown and ignored files.
# XXX If the user has other ignore files outside the repo, or
# changes their list of ignore files at run time, we'll
# potentially never see changes to them. We could get the
# client to report to us what ignore data they're using.
# But it's easier to do nothing than to open that can of
# worms.
Matt Mackall
inotify: fixup rebuilding ignore
r7085 if '_ignore' in self.repo.dirstate.__dict__:
delattr(self.repo.dirstate, '_ignore')
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('rescanning due to .hgignore change\n'))
Nicolas Dumazet
inotify: do not recurse in handle_timeout(): call it explicitely, not in scan()...
r8604 self.handle_timeout()
Bryan O'Sullivan
Add inotify extension
r6239 self.scan()
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def getstat(self, wpath):
try:
return self.statcache[wpath]
except KeyError:
try:
return self.stat(wpath)
except OSError, err:
if err.errno != errno.ENOENT:
raise
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def stat(self, wpath):
try:
st = os.lstat(join(self.wprefix, wpath))
ret = st.st_mode, st.st_size, st.st_mtime
self.statcache[wpath] = ret
return ret
Benoit Boissinot
remove unused variables
r7280 except OSError:
Bryan O'Sullivan
Add inotify extension
r6239 self.statcache.pop(wpath, None)
raise
Thomas Arendsen Hein
Spacing cleanup
r6287
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 def eventaction(code):
def decorator(f):
def wrapper(self, wpath):
if code == 'm' and wpath in self.lastevent and \
self.lastevent[wpath] in 'cm':
return
self.lastevent[wpath] = code
self.timeout = 250
f(self, wpath)
wrapper.func_name = f.func_name
return wrapper
return decorator
@eventaction('c')
Bryan O'Sullivan
Add inotify extension
r6239 def created(self, wpath):
if wpath == '.hgignore':
self.update_hgignore()
try:
st = self.stat(wpath)
if stat.S_ISREG(st[0]):
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.updatefile(wpath, st)
Benoit Boissinot
remove unused variables
r7280 except OSError:
Bryan O'Sullivan
Add inotify extension
r6239 pass
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 @eventaction('m')
Bryan O'Sullivan
Add inotify extension
r6239 def modified(self, wpath):
if wpath == '.hgignore':
self.update_hgignore()
try:
st = self.stat(wpath)
if stat.S_ISREG(st[0]):
if self.repo.dirstate[wpath] in 'lmn':
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.updatefile(wpath, st)
Bryan O'Sullivan
Add inotify extension
r6239 except OSError:
pass
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 @eventaction('d')
Bryan O'Sullivan
Add inotify extension
r6239 def deleted(self, wpath):
if wpath == '.hgignore':
self.update_hgignore()
elif wpath.startswith('.hg/'):
if wpath == '.hg/wlock':
self.check_dirstate()
return
Nicolas Dumazet
inotify: server: refactor updatestatus()...
r8599 self.deletefile(wpath, self.repo.dirstate[wpath])
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 def process_create(self, wpath, evt):
if self.ui.debugflag:
self.ui.note(_('%s event: created %s\n') %
(self.event_time(), wpath))
if evt.mask & inotify.IN_ISDIR:
self.scan(wpath)
else:
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 self.created(wpath)
Bryan O'Sullivan
Add inotify extension
r6239
def process_delete(self, wpath, evt):
if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s event: deleted %s\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time(), wpath))
if evt.mask & inotify.IN_ISDIR:
Nicolas Dumazet
inotify: proper fix for issue1542 (partially reverting 67e59a9886d5)...
r8600 tree = self.dir(self.tree, wpath).copy()
for wfn, ignore in self.walk('?', tree):
self.deletefile(join(wpath, wfn), '?')
Bryan O'Sullivan
Add inotify extension
r6239 self.scan(wpath)
Nicolas Dumazet
inotify: proper fix for issue1542 (partially reverting 67e59a9886d5)...
r8600 else:
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 self.deleted(wpath)
Bryan O'Sullivan
Add inotify extension
r6239
def process_modify(self, wpath, evt):
if self.ui.debugflag:
self.ui.note(_('%s event: modified %s\n') %
(self.event_time(), wpath))
if not (evt.mask & inotify.IN_ISDIR):
Nicolas Dumazet
inotify: use a decorator instead of dispatching calls
r8606 self.modified(wpath)
Bryan O'Sullivan
Add inotify extension
r6239
def process_unmount(self, evt):
self.ui.warn(_('filesystem containing %s was unmounted\n') %
evt.fullpath)
sys.exit(0)
Nicolas Dumazet
inotify: process all inotify events in one batch...
r8609 def handle_pollevents(self, events):
Bryan O'Sullivan
Add inotify extension
r6239 if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s readable: %d bytes\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time(), self.threshold.readable()))
if not self.threshold():
if self.registered:
if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s below threshold - unhooking\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time()))
Nicolas Dumazet
inotify: handle_event: do not use event and fd parameters....
r8607 self.master.poll.unregister(self.fileno())
Bryan O'Sullivan
Add inotify extension
r6239 self.registered = False
self.timeout = 250
else:
self.read_events()
def read_events(self, bufsize=None):
events = self.watcher.read(bufsize)
if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s reading %d events\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time(), len(events)))
for evt in events:
wpath = self.wpath(evt)
if evt.mask & inotify.IN_UNMOUNT:
self.process_unmount(wpath, evt)
elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB):
self.process_modify(wpath, evt)
elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF |
inotify.IN_MOVED_FROM):
self.process_delete(wpath, evt)
elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO):
self.process_create(wpath, evt)
Nicolas Dumazet
inotify: do not defer inotify events processing...
r8605 self.lastevent.clear()
Bryan O'Sullivan
Add inotify extension
r6239 def handle_timeout(self):
if not self.registered:
if self.ui.debugflag:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('%s hooking back up with %d bytes readable\n') %
Bryan O'Sullivan
Add inotify extension
r6239 (self.event_time(), self.threshold.readable()))
self.read_events(0)
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 self.master.poll.register(self, self.poll_events)
Bryan O'Sullivan
Add inotify extension
r6239 self.registered = True
self.timeout = None
def shutdown(self):
self.watcher.close()
Nicolas Dumazet
inotify: introduce debuginotify, which lists which paths are under watch
r8555 def debug(self):
"""
Returns a sorted list of relatives paths currently watched,
for debugging purposes.
"""
return sorted(tuple[0][len(self.wprefix):] for tuple in self.watcher)
Nicolas Dumazet
inotify: server: use a common 'pollable' interface for server & repowatcher...
r8610 class server(pollable):
"""
Listens for client queries on unix socket inotify.sock
"""
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 def __init__(self, ui, repo, repowatcher, timeout):
Bryan O'Sullivan
Add inotify extension
r6239 self.ui = ui
self.repo = repo
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 self.repowatcher = repowatcher
Bryan O'Sullivan
Add inotify extension
r6239 self.timeout = timeout
self.sock = socket.socket(socket.AF_UNIX)
self.sockpath = self.repo.join('inotify.sock')
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 self.realsockpath = None
Bryan O'Sullivan
Add inotify extension
r6239 try:
self.sock.bind(self.sockpath)
except socket.error, err:
if err[0] == errno.EADDRINUSE:
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 raise AlreadyStartedException(_('could not start server: %s')
Bryan O'Sullivan
Add inotify extension
r6239 % err[1])
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 if err[0] == "AF_UNIX path too long":
tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
self.realsockpath = os.path.join(tempdir, "inotify.sock")
try:
self.sock.bind(self.realsockpath)
os.symlink(self.realsockpath, self.sockpath)
except (OSError, socket.error), inst:
try:
os.unlink(self.realsockpath)
except:
pass
os.rmdir(tempdir)
if inst.errno == errno.EEXIST:
raise AlreadyStartedException(_('could not start server: %s')
% inst.strerror)
raise
else:
raise
Bryan O'Sullivan
Add inotify extension
r6239 self.sock.listen(5)
self.fileno = self.sock.fileno
def handle_timeout(self):
pass
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 def answer_stat_query(self, cs):
Bryan O'Sullivan
Add inotify extension
r6239 names = cs.read().split('\0')
Thomas Arendsen Hein
Spacing cleanup
r6287
Bryan O'Sullivan
Add inotify extension
r6239 states = names.pop()
self.ui.note(_('answering query for %r\n') % states)
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 if self.repowatcher.timeout:
Bryan O'Sullivan
Add inotify extension
r6239 # We got a query while a rescan is pending. Make sure we
# rescan before responding, or we could give back a wrong
# answer.
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 self.repowatcher.handle_timeout()
Bryan O'Sullivan
Add inotify extension
r6239
if not names:
def genresult(states, tree):
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 for fn, state in self.repowatcher.walk(states, tree):
Bryan O'Sullivan
Add inotify extension
r6239 yield fn
else:
def genresult(states, tree):
for fn in names:
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 l = self.repowatcher.lookup(fn, tree)
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 try:
if l in states:
yield fn
except TypeError:
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 for f, s in self.repowatcher.walk(states, l, fn):
Benoit Boissinot
Backed out changeset c5dbe86b0fee (issue1375)
r7351 yield f
Bryan O'Sullivan
Add inotify extension
r6239
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 return ['\0'.join(r) for r in [
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 genresult('l', self.repowatcher.statustrees['l']),
genresult('m', self.repowatcher.statustrees['m']),
genresult('a', self.repowatcher.statustrees['a']),
genresult('r', self.repowatcher.statustrees['r']),
genresult('!', self.repowatcher.statustrees['!']),
'?' in states
and genresult('?', self.repowatcher.statustrees['?'])
or [],
Bryan O'Sullivan
Add inotify extension
r6239 [],
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 'c' in states and genresult('n', self.repowatcher.tree) or [],
Bryan O'Sullivan
Add inotify extension
r6239 ]]
Nicolas Dumazet
inotify: introduce debuginotify, which lists which paths are under watch
r8555 def answer_dbug_query(self):
return ['\0'.join(self.repowatcher.debug())]
Nicolas Dumazet
inotify: process all inotify events in one batch...
r8609 def handle_pollevents(self, events):
for e in events:
self.handle_pollevent()
Nicolas Dumazet
inotify: rename handle_event to handle_pollevent to avoid confusion...
r8608 def handle_pollevent(self):
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 sock, addr = self.sock.accept()
cs = common.recvcs(sock)
version = ord(cs.read(1))
if version != common.version:
self.ui.warn(_('received query from incompatible client '
'version %d\n') % version)
return
type = cs.read(4)
if type == 'STAT':
results = self.answer_stat_query(cs)
Nicolas Dumazet
inotify: introduce debuginotify, which lists which paths are under watch
r8555 elif type == 'DBUG':
results = self.answer_dbug_query()
Nicolas Dumazet
inotify: put STAT-specific query answer generation part in its own method
r8554 else:
self.ui.warn(_('unrecognized query type: %s\n') % type)
return
Bryan O'Sullivan
Add inotify extension
r6239 try:
try:
Nicolas Dumazet
inotify: Abstract the layer format and sizes to a inotify.common dictionary...
r8386 v = chr(common.version)
Nicolas Dumazet
inotify: change protocol so that different query types can be supported.
r8553 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
*map(len, results)))
Bryan O'Sullivan
Add inotify extension
r6239 sock.sendall(''.join(results))
finally:
sock.shutdown(socket.SHUT_WR)
except socket.error, err:
if err[0] != errno.EPIPE:
raise
def shutdown(self):
self.sock.close()
try:
os.unlink(self.sockpath)
Benoit Boissinot
inotify: workaround ENAMETOOLONG by using symlinks...
r6997 if self.realsockpath:
os.unlink(self.realsockpath)
os.rmdir(os.path.dirname(self.realsockpath))
Bryan O'Sullivan
Add inotify extension
r6239 except OSError, err:
if err.errno != errno.ENOENT:
raise
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 class master(object):
Bryan O'Sullivan
Add inotify extension
r6239 def __init__(self, ui, repo, timeout=None):
self.ui = ui
self.repo = repo
self.poll = select.poll()
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 self.repowatcher = repowatcher(ui, repo, self)
self.server = server(ui, repo, self.repowatcher, timeout)
Bryan O'Sullivan
Add inotify extension
r6239 self.table = {}
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 for obj in (self.repowatcher, self.server):
Bryan O'Sullivan
Add inotify extension
r6239 fd = obj.fileno()
self.table[fd] = obj
self.poll.register(fd, obj.poll_events)
def register(self, fd, mask):
self.poll.register(fd, mask)
def shutdown(self):
for obj in self.table.itervalues():
obj.shutdown()
def run(self):
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 self.repowatcher.setup()
Bryan O'Sullivan
Add inotify extension
r6239 self.ui.note(_('finished setup\n'))
if os.getenv('TIME_STARTUP'):
sys.exit(0)
while True:
timeout = None
timeobj = None
for obj in self.table.itervalues():
if obj.timeout is not None and (timeout is None or obj.timeout < timeout):
timeout, timeobj = obj.timeout, obj
try:
if self.ui.debugflag:
if timeout is None:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('polling: no timeout\n'))
Bryan O'Sullivan
Add inotify extension
r6239 else:
Martin Geisler
i18n: mark strings for translation in inotify extension
r6961 self.ui.note(_('polling: %sms timeout\n') % timeout)
Bryan O'Sullivan
Add inotify extension
r6239 events = self.poll.poll(timeout)
except select.error, err:
if err[0] == errno.EINTR:
continue
raise
if events:
Nicolas Dumazet
inotify: process all inotify events in one batch...
r8609 by_fd = {}
Bryan O'Sullivan
Add inotify extension
r6239 for fd, event in events:
Nicolas Dumazet
inotify: process all inotify events in one batch...
r8609 by_fd.setdefault(fd, []).append(event)
for fd, events in by_fd.iteritems():
self.table[fd].handle_pollevents(events)
Bryan O'Sullivan
Add inotify extension
r6239 elif timeobj:
timeobj.handle_timeout()
def start(ui, repo):
Brendan Cully
inotify: close most file descriptors when autostarting...
r7451 def closefds(ignore):
# (from python bug #1177468)
# close all inherited file descriptors
# Python 2.4.1 and later use /dev/urandom to seed the random module's RNG
# a file descriptor is kept internally as os._urandomfd (created on demand
# the first time os.urandom() is called), and should not be closed
try:
os.urandom(4)
urandom_fd = getattr(os, '_urandomfd', None)
except AttributeError:
urandom_fd = None
ignore.append(urandom_fd)
for fd in range(3, 256):
if fd in ignore:
continue
try:
os.close(fd)
except OSError:
pass
Nicolas Dumazet
inotify: Coding Style: name classes in lowercase.
r8385 m = master(ui, repo)
Bryan O'Sullivan
Add inotify extension
r6239 sys.stdout.flush()
sys.stderr.flush()
pid = os.fork()
if pid:
return pid
Nicolas Dumazet
inotify: Clarify the use of "watcher" name....
r8335 closefds([m.server.fileno(), m.repowatcher.fileno()])
Bryan O'Sullivan
Add inotify extension
r6239 os.setsid()
fd = os.open('/dev/null', os.O_RDONLY)
os.dup2(fd, 0)
if fd > 0:
os.close(fd)
fd = os.open(ui.config('inotify', 'log', '/dev/null'),
os.O_RDWR | os.O_CREAT | os.O_TRUNC)
os.dup2(fd, 1)
os.dup2(fd, 2)
if fd > 2:
os.close(fd)
try:
m.run()
finally:
m.shutdown()
os._exit(0)