##// END OF EJS Templates
hg: obtain lock when creating share from pooled repo (issue5104)...
hg: obtain lock when creating share from pooled repo (issue5104) There are race conditions between clients performing a shared clone to pooled storage: 1) Clients race to create the new shared repo in the pool directory 2) 1 client is seeding the repo in the pool directory and another goes to share it before it is fully cloned We prevent these race conditions by obtaining a lock in the pool directory that is derived from the name of the repo we will be accessing. To test this, a simple generic "lockdelay" extension has been added. The extension inserts an optional, configurable delay before or after lock acquisition. In the test, we delay 2 seconds after lock acquisition in the first process and 1 second before lock acquisition in the 2nd process. This means the first process has 1s to obtain the lock. There is a race condition here. If we encounter it in the wild, we could change the dummy extension to wait on the lock file to appear instead of relying on timing. But that's more complicated. Let's see what happens first.

File last commit:

r28250:6d0d1173 default
r28289:d493d647 3.7.2 stable
Show More
__init__.py
193 lines | 6.2 KiB | text/x-python | PythonLexer
Matt Mackall
zeroconf: initial implementation...
r7071 # zeroconf.py - zeroconf support for Mercurial
#
# Copyright 2005-2007 Matt Mackall <mpm@selenic.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
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Matt Mackall
zeroconf: initial implementation...
r7071
Cédric Duval
extensions: improve the consistency of synopses...
r8894 '''discover and advertise repositories on the local network
David Soria Parra
zeroconf: add extension documentation
r7606
Martin Geisler
zeroconf: small fixes in docstring...
r11504 Zeroconf-enabled repositories will be announced in a network without
Martin Geisler
zeroconf: word-wrap help texts at 70 characters
r8003 the need to configure a server or a service. They can be discovered
without knowing their actual IP address.
David Soria Parra
zeroconf: add extension documentation
r7606
Martin Geisler
zeroconf: small fixes in docstring...
r11504 To allow other people to discover your repository using run
:hg:`serve` in your repository::
David Soria Parra
zeroconf: add extension documentation
r7606
Martin Geisler
zeroconf: use reST syntax for literal blocks
r9218 $ cd test
$ hg serve
David Soria Parra
zeroconf: add extension documentation
r7606
Martin Geisler
zeroconf: small fixes in docstring...
r11504 You can discover Zeroconf-enabled repositories by running
:hg:`paths`::
David Soria Parra
zeroconf: add extension documentation
r7606
Martin Geisler
zeroconf: use reST syntax for literal blocks
r9218 $ hg paths
zc-test = http://example.com:8000/test
David Soria Parra
zeroconf: add extension documentation
r7606 '''
Renato Cunha
hgext/zeroconf/__init__.py: Separate relative and absolute imports....
r11340 import socket, time, os
import Zeroconf
Benoit Boissinot
zeroconf: use port from server instead of picking port from config (issue3746)...
r18190 from mercurial import ui, hg, encoding, dispatch
Matt Mackall
extensions: use new wrapper functions
r7216 from mercurial import extensions
Benoit Boissinot
zeroconf: use port from server instead of picking port from config (issue3746)...
r18190 from mercurial.hgweb import server as servermod
Matt Mackall
zeroconf: initial implementation...
r7071
Augie Fackler
extensions: document that `testedwith = 'internal'` is special...
r25186 # Note for extension authors: ONLY specify testedwith = 'internal' for
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
Augie Fackler
hgext: mark all first-party extensions as such
r16743 testedwith = 'internal'
Matt Mackall
zeroconf: initial implementation...
r7071 # publish
server = None
localip = None
def getip():
# finds external-facing interface without sending any packets (Linux)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('1.0.0.1', 0))
ip = s.getsockname()[0]
return ip
Brodie Rao
cleanup: replace naked excepts with more specific ones
r16688 except socket.error:
Matt Mackall
zeroconf: initial implementation...
r7071 pass
# Generic method, sometimes gives useless results
Alexander Solovyov
zeroconf: guess ip for Zeroconf...
r8264 try:
dumbip = socket.gethostbyaddr(socket.gethostname())[2][0]
if not dumbip.startswith('127.') and ':' not in dumbip:
return dumbip
Augie Fackler
zeroconf: gethostbyaddr may also fail with socket.herror
r10317 except (socket.gaierror, socket.herror):
Alexander Solovyov
zeroconf: guess ip for Zeroconf...
r8264 dumbip = '127.0.0.1'
Matt Mackall
zeroconf: initial implementation...
r7071
# works elsewhere, but actually sends a packet
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('1.0.0.1', 1))
ip = s.getsockname()[0]
return ip
Brodie Rao
cleanup: replace naked excepts with more specific ones
r16688 except socket.error:
Matt Mackall
zeroconf: initial implementation...
r7071 pass
return dumbip
def publish(name, desc, path, port):
global server, localip
if not server:
Alexander Solovyov
zeroconf: guess ip for Zeroconf...
r8264 ip = getip()
if ip.startswith('127.'):
Augie Fackler
zeroconf: Don't break serve if no internet connection is present.
r7295 # if we have no internet connection, this can happen.
return
Matt Mackall
zeroconf: initial implementation...
r7071 localip = socket.inet_aton(ip)
Alexander Solovyov
zeroconf: guess ip for Zeroconf...
r8264 server = Zeroconf.Zeroconf(ip)
Matt Mackall
zeroconf: initial implementation...
r7071
Alexander Solovyov
zeroconf: advertise repositories with hostname
r7845 hostname = socket.gethostname().split('.')[0]
host = hostname + ".local"
name = "%s-%s" % (hostname, name)
Matt Mackall
zeroconf: initial implementation...
r7071
# advertise to browsers
svc = Zeroconf.ServiceInfo('_http._tcp.local.',
name + '._http._tcp.local.',
server = host,
port = port,
properties = {'description': desc,
'path': "/" + path},
address = localip, weight = 0, priority = 0)
server.registerService(svc)
# advertise to Mercurial clients
svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
name + '._hg._tcp.local.',
Matt Mackall
zeroconf: advertise a proper hostname for _hg services
r7088 server = host,
Matt Mackall
zeroconf: initial implementation...
r7071 port = port,
properties = {'description': desc,
'path': "/" + path},
address = localip, weight = 0, priority = 0)
server.registerService(svc)
Benoit Boissinot
zeroconf: use port from server instead of picking port from config (issue3746)...
r18190 def zc_create_server(create_server, ui, app):
httpd = create_server(ui, app)
port = httpd.port
Matt Mackall
zeroconf: initial implementation...
r7071
Benoit Boissinot
zeroconf: use port from server instead of picking port from config (issue3746)...
r18190 try:
repos = app.repos
except AttributeError:
# single repo
Gregory Szorc
zeroconf: access repo on hgweb_mod properly (issue5036)...
r27910 with app._obtainrepo() as repo:
name = app.reponame or os.path.basename(repo.root)
path = repo.ui.config("web", "prefix", "").strip('/')
desc = repo.ui.config("web", "description", name)
Benoit Boissinot
zeroconf: use port from server instead of picking port from config (issue3746)...
r18190 publish(name, desc, path, port)
else:
# webdir
prefix = app.ui.config("web", "prefix", "").strip('/') + '/'
for repo, path in repos:
u = app.ui.copy()
Alexander Solovyov
zeroconf: code cleanup, fixing variable names to be meaningful
r9488 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
name = os.path.basename(repo)
path = (prefix + repo).strip('/')
Alexander Solovyov
zeroconf: read actual description for repos in hgwebdir
r9489 desc = u.config('web', 'description', name)
Benoit Boissinot
zeroconf: use port from server instead of picking port from config (issue3746)...
r18190 publish(name, desc, path, port)
return httpd
Matt Mackall
zeroconf: initial implementation...
r7071
# listen
class listener(object):
def __init__(self):
self.found = {}
def removeService(self, server, type, name):
if repr(name) in self.found:
del self.found[repr(name)]
def addService(self, server, type, name):
self.found[repr(name)] = server.getServiceInfo(type, name)
def getzcpaths():
Alexander Solovyov
zeroconf: guess ip for Zeroconf...
r8264 ip = getip()
if ip.startswith('127.'):
return
server = Zeroconf.Zeroconf(ip)
Matt Mackall
zeroconf: initial implementation...
r7071 l = listener()
Peter Arrenbrecht
cleanup: drop variables for unused return values...
r7874 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
Matt Mackall
zeroconf: initial implementation...
r7071 time.sleep(1)
server.close()
Alexander Solovyov
zeroconf: code cleanup, fixing variable names to be meaningful
r9488 for value in l.found.values():
name = value.name[:value.name.index('.')]
url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port,
value.properties.get("path", "/"))
yield "zc-" + name, url
Matt Mackall
zeroconf: initial implementation...
r7071
Matt Mackall
extensions: use new wrapper functions
r7216 def config(orig, self, section, key, default=None, untrusted=False):
Matt Mackall
zeroconf: initial implementation...
r7071 if section == "paths" and key.startswith("zc-"):
Alexander Solovyov
zeroconf: code cleanup, fixing variable names to be meaningful
r9488 for name, path in getzcpaths():
if name == key:
return path
Matt Mackall
extensions: use new wrapper functions
r7216 return orig(self, section, key, default, untrusted)
Matt Mackall
zeroconf: initial implementation...
r7071
Yuya Nishihara
zeroconf: forward all arguments passed to ui.configitems() wrapper...
r28038 def configitems(orig, self, section, *args, **kwargs):
repos = orig(self, section, *args, **kwargs)
Matt Mackall
zeroconf: initial implementation...
r7071 if section == "paths":
Alexander Solovyov
zeroconf: code cleanup, fixing variable names to be meaningful
r9488 repos += getzcpaths()
return repos
Matt Mackall
zeroconf: initial implementation...
r7071
Henrik Stuart
zeroconf: override default destination folder on clone
r10342 def defaultdest(orig, source):
for name, path in getzcpaths():
if path == source:
return name.encode(encoding.encoding)
return orig(source)
Nicolas Dumazet
zeroconf: notify the Zeroconf threads when hg exits...
r14104 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
try:
return orig(ui, options, cmd, cmdfunc)
finally:
# we need to call close() on the server to notify() the various
# threading Conditions and allow the background threads to exit
global server
if server:
server.close()
extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
Matt Mackall
extensions: use new wrapper functions
r7216 extensions.wrapfunction(ui.ui, 'config', config)
extensions.wrapfunction(ui.ui, 'configitems', configitems)
Henrik Stuart
zeroconf: override default destination folder on clone
r10342 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
Benoit Boissinot
zeroconf: use port from server instead of picking port from config (issue3746)...
r18190 extensions.wrapfunction(servermod, 'create_server', zc_create_server)