Comments
Patch
@@ -147,19 +147,45 @@
'deerialize': serialize.identity,
},
}
-def makeserver(repo):
- """Return an initialized metaserver instance.
- Updates all indices to the last revision along the way.
+def makeindexstores(repo):
+ """Return a list with the initialized indice stores.
+
+ Updates all indices to the tip of the serverrepo.
+
+ If the store appears to be in an inconsistent state, rebuilds it.
+
+ A simple locking mechanism is used to ensure the consistency of the store.
+ Before modifying the store a lock is acquired (the LOCK file is created
+ on disk). This lock is later released (the file removed) only if
+ the transaction completes successfully.
+ If the lock could not be acquired (it was already taken by someone else) it
+ is very likely that previous transction was aborted and the store is not
+ in the consistent state. In this case all indices are erased and recreated.
"""
opener = scmutil.opener(os.path.join(repo.path, 'hgext/speedyserver'))
opener.makedirs()
- lastrev = len(repo) - 1
- indices = {}
+ lock = store.lock(os.path.join(repo.path, 'hgext/speedyserver/LOCK'))
+ recreate = False
+ try:
+ lock.acquire()
+ except store.LockHeld:
+ recreate = True
+ istores = []
+ updaterev = len(repo) - 1
for name, cfg in indicecfg.iteritems():
istore = store.indexstore(name, opener, **cfg)
- istore.update(repo, lastrev)
- indices[name] = istore.view()
+ if recreate:
+ istore.clear()
+ istore.update(repo, updaterev)
+ istores.append(istore)
+ lock.release()
+ return istores
+
+def makeserver(repo):
+ """Return an initialized metaserver instance."""
+ istores = makeindexstores(repo)
+ indices = dict([(istore.name, istore.view()) for istore in istores])
return metaserver(repo, indices)
@command('metaserve', [], _(''))
@@ -174,4 +200,3 @@
serviceopts = collections.defaultdict(lambda: '')
ui.status(_('listening on port %d\n' % port))
cmdutil.service(serviceopts, runfn=server.serve_forever)
-
@@ -57,3 +57,25 @@
def view(self):
"""Return a dict-like object for read access to the index elements."""
return self._dict
+
+class LockHeld(Exception):
+ """Raised by `lock.acquire` if the lock is already taken."""
+ pass
+
+class lock(object):
+ """Simple file-based lock used to ensure store consistency."""
+
+ def __init__(self, path):
+ self.path = path
+
+ def acquire(self):
+ try:
+ ld = os.open(self.path, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
+ os.close(ld)
+ except (OSError, IOError), why:
+ if why.errno == errno.EEXIST:
+ raise LockHeld
+ raise
+
+ def release(self):
+ os.remove(self.path)
@@ -235,6 +235,20 @@
$ cd ..
+Testing store corruption detection
+
+ $ cd $TESTTMP/serverrepo
+
+ $ touch .hg/hgext/speedyserver/LOCK
+ $ rm -rf .hg/hgext/speedyserver/files .hg/hgext/speedyserver/filechgs
+
+ $ cd $TESTTMP/localrepo
+
+ $ hg log "glob:d2/*" --exclude "**.py"
+ chgx
+ chgpushed
+ chg4
+
Testing socket server
Writing server config file
@@ -364,3 +378,4 @@
$ kill `cat pidfile` 2> /dev/null
$ cat err
handling request
+