From patchwork Tue Sep 17 01:47:55 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: hg serve: an option to use the first unused port above a given number From: Roman Neuhauser X-Patchwork-Id: 2505 Message-Id: <20130917014755.GD71308@isis.sigpipe.cz> To: mercurial-devel@selenic.com Date: Tue, 17 Sep 2013 03:47:55 +0200 # neuhauser@sigpipe.cz / 2013-09-17 03:26:56 +0200: > # kbullock+mercurial@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 # 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 < [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))