devtools: new workflow for multiple simultaneous `mojo_run` runs.
This patch provides a new workflow for multiple simultaneous runs of
`mojo_run`. In the new workflow first instance of `mojo_run` is started
normally, while the subsequent ones get the `--reuse-servers` flag.
`--reuse-servers` skips spawning any dev servers, instead it assumes
that they are already running and only sets up port forwarding if
needed. This preserves caching and should work over adb_remote_setup.
The previous workaround switches: `--free-ports` and `--free-host-ports`
are removed.
Fixes https://github.com/domokit/devtools/issues/55 .
R=qsr@chromium.org
Review URL: https://codereview.chromium.org/1436503002 .
diff --git a/mojo/devtools/common/devtoolslib/android_shell.py b/mojo/devtools/common/devtoolslib/android_shell.py
index 400195c..7f69c08 100644
--- a/mojo/devtools/common/devtoolslib/android_shell.py
+++ b/mojo/devtools/common/devtoolslib/android_shell.py
@@ -16,7 +16,7 @@
import time
import uuid
-from devtoolslib.http_server import start_http_server
+from devtoolslib import http_server
from devtoolslib.shell import Shell
from devtoolslib.utils import overrides
@@ -421,10 +421,13 @@
logcat_watch_thread.start()
@overrides(Shell)
- def serve_local_directories(self, mappings, port=0, free_host_port=False):
+ def serve_local_directories(self, mappings, port=0, reuse_servers=False):
assert mappings
- host_port = 0 if free_host_port else port
- server_address = start_http_server(mappings, host_port=host_port)
+ if reuse_servers:
+ assert port, 'Cannot reuse the server when |port| is 0.'
+ server_address = ('127.0.0.1', port)
+ else:
+ server_address = http_server.start_http_server(mappings, port)
return 'http://127.0.0.1:%d/' % self._forward_device_port_to_host(
port, server_address[1])
diff --git a/mojo/devtools/common/devtoolslib/linux_shell.py b/mojo/devtools/common/devtoolslib/linux_shell.py
index 48a2859..8a0ec7f 100644
--- a/mojo/devtools/common/devtoolslib/linux_shell.py
+++ b/mojo/devtools/common/devtoolslib/linux_shell.py
@@ -25,8 +25,14 @@
self.command_prefix = command_prefix if command_prefix else []
@overrides(Shell)
- def serve_local_directories(self, mappings, port=0, free_host_port=False):
- return 'http://%s:%d/' % http_server.start_http_server(mappings, port)
+ def serve_local_directories(self, mappings, port=0, reuse_servers=False):
+ if reuse_servers:
+ assert port, 'Cannot reuse the server when |port| is 0.'
+ server_address = ('127.0.0.1', port)
+ else:
+ server_address = http_server.start_http_server(mappings, port)
+
+ return 'http://%s:%d/' % server_address
@overrides(Shell)
def forward_host_port_to_shell(self, host_port):
diff --git a/mojo/devtools/common/devtoolslib/shell.py b/mojo/devtools/common/devtoolslib/shell.py
index 7e1d395..bc47856 100644
--- a/mojo/devtools/common/devtoolslib/shell.py
+++ b/mojo/devtools/common/devtoolslib/shell.py
@@ -6,7 +6,7 @@
class Shell(object):
"""Represents an abstract Mojo shell."""
- def serve_local_directories(self, mappings, port=0, free_host_port=False):
+ def serve_local_directories(self, mappings, port=0, reuse_servers=False):
"""Serves the content of the local (host) directories, making it available
to the shell under the url returned by the function.
@@ -22,9 +22,9 @@
port: port at which the server will be available to the shell. On Android
this can be different from the port on which the server runs on the
host.
- free_host_port: spawn the server a system allocated port. This is ignored
- on Linux, where |port| indicates the port on which the server will be
- spawned.
+ reuse_servers: don't actually spawn the server. Instead assume that the
+ server is already running on |port|, and only set up forwarding if
+ needed.
Returns:
The url that the shell can use to access the server.
diff --git a/mojo/devtools/common/devtoolslib/shell_arguments.py b/mojo/devtools/common/devtoolslib/shell_arguments.py
index 65e6d3c..024ee51 100644
--- a/mojo/devtools/common/devtoolslib/shell_arguments.py
+++ b/mojo/devtools/common/devtoolslib/shell_arguments.py
@@ -26,7 +26,7 @@
return True if urlparse.urlparse(dest).scheme else False
-def _host_local_url_destination(shell, dest_file, port, free_host_port):
+def _host_local_url_destination(shell, dest_file, port, reuse_servers):
"""Starts a local server to host |dest_file|.
Returns:
@@ -37,21 +37,21 @@
raise ValueError('local path passed as --map-url destination '
'does not exist')
mappings = [('', [directory])]
- server_url = shell.serve_local_directories(mappings, port, free_host_port)
+ server_url = shell.serve_local_directories(mappings, port, reuse_servers)
return server_url + os.path.relpath(dest_file, directory)
-def _host_local_origin_destination(shell, dest_dir, port, free_host_port):
+def _host_local_origin_destination(shell, dest_dir, port, reuse_servers):
"""Starts a local server to host |dest_dir|.
Returns:
Url of the hosted directory.
"""
mappings = [('', [dest_dir])]
- return shell.serve_local_directories(mappings, port, free_host_port)
+ return shell.serve_local_directories(mappings, port, reuse_servers)
-def _rewrite(mapping, host_destination_functon, shell, port, free_host_port):
+def _rewrite(mapping, host_destination_functon, shell, port, reuse_servers):
"""Takes a mapping given as <src>=<dest> and rewrites the <dest> part to be
hosted locally using the given function if <dest> is not a web url.
"""
@@ -64,12 +64,12 @@
return mapping
src = parts[0]
- dest = host_destination_functon(shell, parts[1], port, free_host_port)
+ dest = host_destination_functon(shell, parts[1], port, reuse_servers)
return src + '=' + dest
def _apply_mappings(shell, original_arguments, map_urls, map_origins,
- free_ports, free_host_ports):
+ reuse_servers):
"""Applies mappings for specified urls and origins. For each local path
specified as destination a local server will be spawned and the mapping will
be rewritten accordingly.
@@ -81,39 +81,34 @@
<url>=<url-or-local-path>.
map_origins: List of origin mappings, each in the form of
<origin>=<url-or-local-path>.
- free_ports: Iff True, run local development servers on system-allocated
- ports. This defeats any performance benefits from caching.
- free_host_ports: Only applicable on Android. Iff True, local development
- servers are run on system-allocated ports, but are still forwarded from
- fixed ports on the device.
+ reuse_servers: Assume that the development servers are already running and
+ do not spawn any.
Returns:
The updated argument list.
"""
- next_port = 0 if free_ports else _MAPPINGS_BASE_PORT
+ next_port = _MAPPINGS_BASE_PORT
args = original_arguments
if map_urls:
# Sort the mappings to preserve caching regardless of argument order.
for map_url in sorted(map_urls):
mapping = _rewrite(map_url, _host_local_url_destination, shell, next_port,
- free_host_ports)
- if not free_ports:
- next_port += 1
+ reuse_servers)
+ next_port += 1
# All url mappings need to be coalesced into one shell argument.
args = append_to_argument(args, '--url-mappings=', mapping)
if map_origins:
for map_origin in sorted(map_origins):
mapping = _rewrite(map_origin, _host_local_origin_destination, shell,
- next_port, free_host_ports)
- if not free_ports:
- next_port += 1
+ next_port, reuse_servers)
+ next_port += 1
# Origin mappings are specified as separate, repeated shell arguments.
args.append('--map-origin=' + mapping)
return args
-def configure_local_origin(shell, local_dir, port, free_host_port):
+def configure_local_origin(shell, local_dir, port, reuse_servers):
"""Sets up a local http server to serve files in |local_dir| along with
device port forwarding if needed.
@@ -121,7 +116,7 @@
The list of arguments to be appended to the shell argument list.
"""
mappings = [('', [local_dir])]
- origin_url = shell.serve_local_directories(mappings, port, free_host_port)
+ origin_url = shell.serve_local_directories(mappings, port, reuse_servers)
return ["--origin=" + origin_url]
@@ -155,7 +150,7 @@
return arguments
-def _configure_dev_server(shell, shell_args, dev_server_config, free_host_port,
+def _configure_dev_server(shell, shell_args, dev_server_config, reuse_servers,
verbose):
"""Sets up a dev server on the host according to |dev_server_config|.
@@ -169,9 +164,8 @@
The updated argument list.
"""
port = dev_server_config.port if dev_server_config.port else 0
- server_url = shell.serve_local_directories(dev_server_config.mappings,
- port=port,
- free_host_port=free_host_port)
+ server_url = shell.serve_local_directories(
+ dev_server_config.mappings, port, reuse_servers)
shell_args.append('--map-origin=%s=%s' % (dev_server_config.host, server_url))
if verbose:
@@ -223,17 +217,16 @@
shell_args = _apply_mappings(shell, shell_args, shell_config.map_url_list,
shell_config.map_origin_list,
- shell_config.free_ports,
- shell_config.free_host_ports)
+ shell_config.reuse_servers)
if shell_config.origin:
if _is_web_url(shell_config.origin):
shell_args.append('--origin=' + shell_config.origin)
else:
- local_origin_port = 0 if shell_config.free_ports else _LOCAL_ORIGIN_PORT
+ local_origin_port = _LOCAL_ORIGIN_PORT
shell_args.extend(configure_local_origin(shell, shell_config.origin,
local_origin_port,
- shell_config.free_host_ports))
+ shell_config.reuse_servers))
if shell_config.content_handlers:
for (mime_type,
@@ -244,7 +237,7 @@
for dev_server_config in shell_config.dev_servers:
shell_args = _configure_dev_server(shell, shell_args, dev_server_config,
- shell_config.free_host_ports,
+ shell_config.reuse_servers,
shell_config.verbose)
return shell, shell_args
diff --git a/mojo/devtools/common/devtoolslib/shell_config.py b/mojo/devtools/common/devtoolslib/shell_config.py
index 79a14c2..fde356f 100644
--- a/mojo/devtools/common/devtoolslib/shell_config.py
+++ b/mojo/devtools/common/devtoolslib/shell_config.py
@@ -29,7 +29,7 @@
self.map_url_list = []
self.map_origin_list = []
self.dev_servers = []
- self.free_ports = False
+ self.reuse_servers = False
self.content_handlers = dict()
self.verbose = None
@@ -38,7 +38,6 @@
self.target_device = None
self.logcat_tags = None
self.require_root = False
- self.free_host_ports = False
# Desktop-only.
self.use_osmesa = None
@@ -70,10 +69,10 @@
parser.add_argument('--map-origin', action='append',
help='Define a mapping for a url origin in the format '
'<origin>=<url-or-local-file-path>')
- parser.add_argument('--free-ports', action='store_true',
- help='Use system-allocated ports when spawning local '
- 'servers. This defeats caching and thus hurts '
- 'performance.')
+ parser.add_argument('--reuse-servers', action='store_true',
+ help='Do not spawn any development servers. Assume that '
+ 'dev servers are already running and only forward them '
+ 'to a device if needed.')
parser.add_argument('-v', '--verbose', action="store_true",
help="Increase output verbosity")
@@ -83,11 +82,6 @@
android_group.add_argument('--target-device', help='Device to run on.')
android_group.add_argument('--logcat-tags', help='Comma-separated list of '
'additional logcat tags to display.')
- android_group.add_argument('--free-host-ports', action='store_true',
- help='Use system-allocated ports on the host when '
- 'spawning local servers. This still forwards to '
- 'fixed ports on the device, so that caching '
- 'works.')
desktop_group = parser.add_argument_group('Desktop-only',
'These arguments apply only when running on desktop.')
@@ -160,7 +154,7 @@
shell_config.origin = script_args.origin
shell_config.map_url_list = script_args.map_url
shell_config.map_origin_list = script_args.map_origin
- shell_config.free_ports = script_args.free_ports
+ shell_config.reuse_servers = script_args.reuse_servers
shell_config.verbose = script_args.verbose
# Android-only.
@@ -168,7 +162,6 @@
'adb')
shell_config.target_device = script_args.target_device
shell_config.logcat_tags = script_args.logcat_tags
- shell_config.free_host_ports = script_args.free_host_ports
# Desktop-only.
shell_config.use_osmesa = script_args.use_osmesa
diff --git a/mojo/devtools/common/docs/mojo_run.md b/mojo/devtools/common/docs/mojo_run.md
index 0675c9c..11758b6 100644
--- a/mojo/devtools/common/docs/mojo_run.md
+++ b/mojo/devtools/common/docs/mojo_run.md
@@ -27,22 +27,45 @@
By default, `mojo_run` uses mojo:kiosk_wm as the window manager. You can pass a
different window manager url using the `--window-manager` flag to override this.
-## Running on multiple Android devices
+## Running multiple instances simultaneously
-Two or more instances of `mojo_run` can simultaneously run on separate Android
-devices. For that, run in individual shells:
+`mojo_run` sets up development servers on fixed ports to facilitate caching
+between runs and allow the script to work remotely using `adb_remote_setup`.
+This would normally prevent two or more instances of `mojo_run` from running
+simulatenously as the development servers cannot be spawned twice on the same
+ports.
+
+In order to run the same set of binaries simultaneously one can use the
+`--reuse-servers` switch for second and further instances. This will make the
+second and further instances assume that development servers are already
+spawned.
+
+On **Android** one needs to indicate the id of the device to be targeted in each
+run. For example, we could run the following in one shell:
```sh
-mojo_run APP_URL --android --target-device DEVICE_ID --free-host-ports
+mojo_run APP_URL --android --target-device DEVICE_ID
```
+and the following in another:
+
```sh
-mojo_run APP_URL --android --target-device ANOTHER_DEVICE_ID --free-host-ports
+mojo_run APP_URL --android --target-device ANOTHER_DEVICE_ID --reuse-servers
```
-`--free-host-ports` makes `mojo_run` spawn the development servers on
-system-allocated ports on the server (so that multiple instances can run in
-parallel) while still forwarding them to fixed ports on the device (so that
-caching still works). This breaks the remote workflow over `adb_remote_setup`.
+Device id can be obtained from `adb devices`.
-DEVICE_ID can be obtained from `adb devices`.
+On **Linux** one needs to use a different $HOME directory for each run, to avoid
+collision of the cache storage. For example, we could run the following in one
+shell:
+
+```sh
+mojo_run APP_URL
+```
+
+and the following in another:
+
+```sh
+mkdir ~/another_home
+HOME=~/another_home mojo_run APP_URL --reuse-servers
+```