diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py
index aad3d4b7b30657e1d8c1eb74917cc30135b8802f..b2af8a6c9c330267f15f4065727dd0e8eb773d74 100755
--- a/indra/llcorehttp/tests/test_llcorehttp_peer.py
+++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py
@@ -34,7 +34,6 @@
 import time
 import select
 import getopt
-from threading import Thread
 try:
     from cStringIO import StringIO
 except ImportError:
@@ -48,7 +47,7 @@
 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
                              "llmessage", "tests"))
 
-from testrunner import run, debug, VERBOSE
+from testrunner import freeport, run, debug, VERBOSE
 
 class TestHTTPRequestHandler(BaseHTTPRequestHandler):
     """This subclass of BaseHTTPRequestHandler is to receive and echo
@@ -303,9 +302,18 @@ def shutdown_request(self, *args, **kwds):
         if option == "-V" or option == "--valgrind":
             do_valgrind = True
 
-    # Instantiate a Server(TestHTTPRequestHandler) on a port chosen by the
-    # runtime.
-    httpd = Server(('127.0.0.1', 0), TestHTTPRequestHandler)
+    # function to make a server with specified port
+    make_server = lambda port: Server(('127.0.0.1', port), TestHTTPRequestHandler)
+
+    if not sys.platform.startswith("win"):
+        # Instantiate a Server(TestHTTPRequestHandler) on a port chosen by the
+        # runtime.
+        httpd = make_server(0)
+    else:
+        # "Then there's Windows"
+        # Instantiate a Server(TestHTTPRequestHandler) on the first free port
+        # in the specified port range.
+        httpd, port = freeport(xrange(8000, 8020), make_server)
 
     # Pass the selected port number to the subject test program via the
     # environment. We don't want to impose requirements on the test program's
diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py
index 8e1204fb20c607f49bd0dbf45563d6f4ff8f626a..9cd2959ea18e59b1999ca72cfeb2ecfd5cfa99ec 100755
--- a/indra/llmessage/tests/test_llsdmessage_peer.py
+++ b/indra/llmessage/tests/test_llsdmessage_peer.py
@@ -31,12 +31,11 @@
 
 import os
 import sys
-from threading import Thread
 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
 
 from llbase.fastest_elementtree import parse as xml_parse
 from llbase import llsd
-from testrunner import run, debug, VERBOSE
+from testrunner import freeport, run, debug, VERBOSE
 import time
 
 _storage=None
@@ -155,9 +154,19 @@ class Server(HTTPServer):
     allow_reuse_address = False
 
 if __name__ == "__main__":
-    # Instantiate a Server(TestHTTPRequestHandler) on a port chosen by the
-    # runtime.
-    httpd = Server(('127.0.0.1', 0), TestHTTPRequestHandler)
+    # function to make a server with specified port
+    make_server = lambda port: Server(('127.0.0.1', port), TestHTTPRequestHandler)
+
+    if not sys.platform.startswith("win"):
+        # Instantiate a Server(TestHTTPRequestHandler) on a port chosen by the
+        # runtime.
+        httpd = make_server(0)
+    else:
+        # "Then there's Windows"
+        # Instantiate a Server(TestHTTPRequestHandler) on the first free port
+        # in the specified port range.
+        httpd, port = freeport(xrange(8000, 8020), make_server)
+
     # Pass the selected port number to the subject test program via the
     # environment. We don't want to impose requirements on the test program's
     # command-line parsing -- and anyway, for C++ integration tests, that's
diff --git a/indra/llmessage/tests/testrunner.py b/indra/llmessage/tests/testrunner.py
index 09f0f3c681407c25087154833a4b7eb4caa7157f..c25945067eb0fd4ab5628fca93174aaef62b42a2 100755
--- a/indra/llmessage/tests/testrunner.py
+++ b/indra/llmessage/tests/testrunner.py
@@ -32,7 +32,7 @@
 import re
 import errno
 import socket
-from threading import Thread
+import subprocess
 
 VERBOSE = os.environ.get("INTEGRATION_TEST_VERBOSE", "0") # default to quiet
 # Support usage such as INTEGRATION_TEST_VERBOSE=off -- distressing to user if
@@ -155,13 +155,13 @@ def run(*args, **kwds):
     In addition, you may pass keyword-only arguments:
 
     use_path=True: allow a simple filename as command and search PATH for that
-    filename. Otherwise the command must be a full pathname.
+    filename. (This argument is retained for backwards compatibility but is
+    now the default behavior.)
 
     server_inst: an instance of a subclass of SocketServer.BaseServer.
 
-    When you pass server_inst, its serve_forever() method is called on a
-    separate Thread before the child process is run. It is shutdown() when the
-    child process terminates.
+    When you pass server_inst, run() calls its handle_request() method in a
+    loop until the child process terminates.
     """
     # server= keyword arg is discontinued
     try:
@@ -171,45 +171,47 @@ def run(*args, **kwds):
     else:
         raise Error("Obsolete call to testrunner.run(): pass server_inst=, not server=")
 
+    debug("Running %s...", " ".join(args))
+
     try:
         server_inst = kwds.pop("server_inst")
     except KeyError:
-        # We're not starting a thread, so shutdown() is a no-op.
-        shutdown = lambda: None
+        # Without server_inst, this is very simple: just run child process.
+        rc = subprocess.call(args)
     else:
-        # Make a Thread on which to call server_inst.serve_forever().
-        thread = Thread(name="server", target=server_inst.serve_forever)
-
-        # Make this a "daemon" thread.
-        thread.setDaemon(True)
-        thread.start()
-
-        # We used to simply call sys.exit() with the daemon thread still
-        # running -- but in recent versions of Python 2, even when you call
-        # sys.exit(0), apparently killing the thread causes the Python runtime
-        # to force the process termination code to 1. So try to play nice.
-        def shutdown():
-            # evidently this call blocks until shutdown is complete
-            server_inst.shutdown()
-            # which should make it straightforward to join()
-            thread.join()
-
-    try:
-        # choice of os.spawnv():
-        # - [v vs. l] pass a list of args vs. individual arguments,
-        # - [no p] don't use the PATH because we specifically want to invoke the
-        #   executable passed as our first arg,
-        # - [no e] child should inherit this process's environment.
-        debug("Running %s...", " ".join(args))
-        if kwds.get("use_path", False):
-            rc = os.spawnvp(os.P_WAIT, args[0], args)
-        else:
-            rc = os.spawnv(os.P_WAIT, args[0], args)
-        debug("%s returned %s", args[0], rc)
-        return rc
-
-    finally:
-        shutdown()
+        # We're being asked to run a local server while the child process
+        # runs. We used to launch a daemon thread calling
+        # server_inst.serve_forever(), then eventually call sys.exit() with
+        # the daemon thread still running -- but in recent versions of Python
+        # 2, even when you call sys.exit(0), apparently killing the thread
+        # causes the Python runtime to force the process termination code
+        # nonzero. So now we avoid the extra thread altogether.
+
+        # SocketServer.BaseServer.handle_request() honors a 'timeout'
+        # attribute, if it's set to something other than None.
+        # We pick 0.5 seconds because that's the default poll timeout for
+        # BaseServer.serve_forever(), which is what we used to use.
+        server_inst.timeout = 0.5
+
+        child = subprocess.Popen(args)
+        while child.poll() is None:
+            # Setting server_inst.timeout is what keeps this handle_request()
+            # call from blocking "forever." Interestingly, looping over
+            # handle_request() with a timeout is very like the implementation
+            # of serve_forever(). We just check a different flag to break out.
+            # It might be interesting if handle_request() returned an
+            # indication of whether it in fact handled a request or timed out.
+            # Oddly, it doesn't. We could discover that by overriding
+            # handle_timeout(), whose default implementation does nothing --
+            # but in fact we really don't care. All that matters is that we
+            # regularly poll both the child process and the server socket.
+            server_inst.handle_request()
+        # We don't bother to capture the rc returned by child.poll() because
+        # poll() is already defined to capture that in its returncode attr.
+        rc = child.returncode
+
+    debug("%s returned %s", args[0], rc)
+    return rc
 
 # ****************************************************************************
 #   test code -- manual at this point, see SWAT-564
diff --git a/indra/newview/tests/test_llxmlrpc_peer.py b/indra/newview/tests/test_llxmlrpc_peer.py
index 12394ad1d94c5c02d6557586f1944a800f7b124c..cff40aa4c2516809595b78b31c3a1a10e0af0f1e 100755
--- a/indra/newview/tests/test_llxmlrpc_peer.py
+++ b/indra/newview/tests/test_llxmlrpc_peer.py
@@ -31,12 +31,11 @@
 
 import os
 import sys
-from threading import Thread
 from SimpleXMLRPCServer import SimpleXMLRPCServer
 
 mydir = os.path.dirname(__file__)       # expected to be .../indra/newview/tests/
 sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "llmessage", "tests"))
-from testrunner import run, debug
+from testrunner import freeport, run, debug
 
 class TestServer(SimpleXMLRPCServer):
     # This server_bind() override is borrowed and simplified from
@@ -76,8 +75,18 @@ def log_error(self, format, *args):
         pass
 
 if __name__ == "__main__":
-    # Make the runtime choose an available port.
-    xmlrpcd = TestServer(('127.0.0.1', 0))
+    # function to make a server with specified port
+    make_server = lambda port: TestServer(('127.0.0.1', port))
+
+    if not sys.platform.startswith("win"):
+        # Instantiate a TestServer on a port chosen by the runtime.
+        xmlrpcd = make_server(0)
+    else:
+        # "Then there's Windows"
+        # Instantiate a TestServer on the first free port in the specified
+        # port range.
+        xmlrpcd, port = freeport(xrange(8000, 8020), make_server)
+
     # Pass the selected port number to the subject test program via the
     # environment. We don't want to impose requirements on the test program's
     # command-line parsing -- and anyway, for C++ integration tests, that's