hg serve: an option to use the first unused port above a given number
Roman Neuhauser
neuhauser at sigpipe.cz
Mon Sep 16 20:47:55 CDT 2013
# neuhauser at sigpipe.cz / 2013-09-17 03:26:56 +0200:
> # kbullock+mercurial at ringworld.org / 2013-09-16 13:20:59 -0500:
> > On 16 Sep 2013, at 11:18 AM, Roman Neuhauser wrote:
> > > i need to run hg-serve in tests for an extension i'm writing.
> > > it looks like hg-serve can only listen on a given port (defaults
> > > to 8000). this makes the tests somewhat brittle: if there are
> > > N concurrent runs, N-1 of them will fail.
> > >
> > > i'd like to have hg-serve optionally bind to the first available
> > > port at-or-above a given number. looking at commands.serve and
> > > hgweb.server.create_server, it shouldn't even be that hard.
> this is for hg-stable, for no particular reason.
> it's not complete (no doc update), i want early feedback.
>
> questions: how can i integrate the test into the mercurial suite?
> is there anything besides mercurial/help/config.txt for docs i need
> to update?
updated patch with web.ports described in mercurial/help/config.txt.
# HG changeset patch
# User Roman Neuhauser <neuhauser at sigpipe.cz>
# Date 1379378142 -7200
# Tue Sep 17 02:35:42 2013 +0200
# Branch stable
# Node ID 706b1155b2a72dfa9873ff6bfdab0d3927276f9c
# Parent 064f7d697852ad6de03b7b2cbf452b69f5de6bef
`hg serve` can use one of multiple ports
A new configuration option, `web.ports` defines the length
of the port range `hg serve` should try.
web.port=8000, web.ports=2 means "try to bind() to 8000 first,
and to 8001 if that fails". Default is 1.
setup:
$ export HGRCPATH="$PWD/hgrc"
$ cat > $HGRCPATH <<EOF
> [ui]
> interactive = False
> [web]
> port = 18000
> ports = 4
> EOF
$ hg init
web.ports = 4 means the fifth will fail:
$ hg serve -d --pid-file 18000.pid
listening at http://stick.suse.cz:18000/ (bound to *:18000)
$ hg serve -d --pid-file 18001.pid
listening at http://stick.suse.cz:18001/ (bound to *:18001)
$ hg serve -d --pid-file 18002.pid
listening at http://stick.suse.cz:18002/ (bound to *:18002)
$ hg serve -d --pid-file 18003.pid
listening at http://stick.suse.cz:18003/ (bound to *:18003)
$ hg serve -d --pid-file 1DEAD.pid
abort: cannot start server at ':18003': Address already in use
abort: child process failed to start
[255]
teardown:
$ kill $(cat *.pid)
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1454,6 +1454,10 @@ The full set of options is:
``port``
Port to listen on. Default is 8000.
+``ports``
+ Listen on the first available port from the ``[port, port + ports)``
+ range. Default is 1.
+
``prefix``
Prefix path to serve from. Default is '' (server root).
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -322,9 +322,18 @@ def create_server(ui, app):
import mimetypes; mimetypes.init()
address = ui.config('web', 'address', '')
- port = util.getport(ui.config('web', 'port', 8000))
- try:
- return cls(ui, app, (address, port), handler)
- except socket.error, inst:
- raise util.Abort(_("cannot start server at '%s:%d': %s")
- % (address, port, inst.args[1]))
+ base = util.getport(ui.config('web', 'port', 8000))
+ ports = ui.configint('web', 'ports', 1)
+ last = base + ports
+ if ports <= 0:
+ raise util.Abort(_("web.ports must be positive"))
+
+ error = 'unknown error'
+ for port in range(base, last):
+ try:
+ return cls(ui, app, (address, port), handler)
+ except socket.error, inst:
+ error = inst.args[1]
+
+ raise util.Abort(_("cannot start server at '%s:%d': %s")
+ % (address, port, error))
--
roman
More information about the Mercurial-devel
mailing list