Patchwork hg serve: an option to use the first unused port above a given number

login
register
mail settings
Submitter Roman Neuhauser
Date Sept. 17, 2013, 1:47 a.m.
Message ID <20130917014755.GD71308@isis.sigpipe.cz>
Download mbox | patch
Permalink /patch/2505/
State Deferred
Headers show

Comments

Roman Neuhauser - Sept. 17, 2013, 1:47 a.m.
# 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 <neuhauser@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)

Patch

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))