Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(238)

Side by Side Diff: build/android/pylib/forwarder.py

Issue 18086004: Move Python setup/tear down logic into Forwarder itself. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Use file lock Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import fcntl
5 import logging 6 import logging
6 import os 7 import os
7 import re 8 import re
8 import sys 9 import sys
9 import threading
10 import time 10 import time
11 11
12 import android_commands 12 import android_commands
13 import cmd_helper 13 import cmd_helper
14 import constants 14 import constants
15 15
16 from pylib import pexpect 16 from pylib import valgrind_tools
17 17
18 18
19 def _MakeBinaryPath(build_type, binary_name): 19 def _MakeBinaryPath(build_type, binary_name):
20 return os.path.join(cmd_helper.OutDirectory.get(), build_type, binary_name) 20 return os.path.join(cmd_helper.OutDirectory.get(), build_type, binary_name)
21 21
22 22
23 class _FileLock(object):
24 """With statement-aware implementation of a file lock.
25
26 File locks are needed for cross-process synchronization when the
27 multiprocessing Python module is used.
28 """
29 def __init__(self, path):
30 self._file = open(path, 'w')
31
32 def __enter__(self):
33 fcntl.flock(self._file, fcntl.LOCK_EX)
bulach 2013/07/12 11:48:26 hmm, I think this needs: fcntl.LOCK_EX | fcntl.LOC
frankf 2013/07/12 21:56:56 Are you suggesting the first process holds the loc
bulach 2013/07/15 09:32:30 afaict, 48 below does hold the EX lock during the
pliard 2013/07/15 09:44:29 Wouldn't the lock be held only in a with statement
bulach 2013/07/15 10:20:03 of course! you're right, the lock would only take
pliard 2013/07/15 10:51:11 If we use the locked file just as you said (i.e. i
34
35 def __exit__(self, type, value, traceback):
36 fcntl.flock(self._file, fcntl.LOCK_UN)
37
38
23 class Forwarder(object): 39 class Forwarder(object):
24 """Thread-safe class to manage port forwards from the device to the host.""" 40 """Thread-safe class to manage port forwards from the device to the host."""
25 41
26 _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR + 42 _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR +
27 '/forwarder/') 43 '/forwarder/')
28 _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR + 44 _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR +
29 '/forwarder/device_forwarder') 45 '/forwarder/device_forwarder')
30 _LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % _DEVICE_FORWARDER_FOLDER 46 _LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % _DEVICE_FORWARDER_FOLDER
31 47
32 def __init__(self, adb, build_type): 48 _lock = _FileLock('/tmp/chrome.forwarder.lock')
33 """Forwards TCP ports on the device back to the host. 49 _instance = None
34 50
35 Works like adb forward, but in reverse. 51 @staticmethod
36 52 def Map(port_pairs, adb, build_type='Debug', tool=None):
37 Args:
38 adb: Instance of AndroidCommands for talking to the device.
39 build_type: 'Release' or 'Debug'.
40 """
41 assert build_type in ('Release', 'Debug')
42 self._adb = adb
43 self._device_to_host_port_map = dict()
44 self._host_to_device_port_map = dict()
45 self._device_initialized = False
46 self._host_adb_control_port = 0
47 self._lock = threading.Lock()
48 self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
49 self._device_forwarder_path_on_host = os.path.join(
50 cmd_helper.OutDirectory.get(), build_type, 'forwarder_dist')
51
52 def Run(self, port_pairs, tool):
53 """Runs the forwarder. 53 """Runs the forwarder.
54 54
55 Args: 55 Args:
56 port_pairs: A list of tuples (device_port, host_port) to forward. Note 56 port_pairs: A list of tuples (device_port, host_port) to forward. Note
57 that you can specify 0 as a device_port, in which case a 57 that you can specify 0 as a device_port, in which case a
58 port will by dynamically assigned on the device. You can 58 port will by dynamically assigned on the device. You can
59 get the number of the assigned port using the 59 get the number of the assigned port using the
60 DevicePortForHostPort method. 60 DevicePortForHostPort method.
61 adb: An AndroidCommands instance.
61 tool: Tool class to use to get wrapper, if necessary, for executing the 62 tool: Tool class to use to get wrapper, if necessary, for executing the
62 forwarder (see valgrind_tools.py). 63 forwarder (see valgrind_tools.py).
63 64
64 Raises: 65 Raises:
65 Exception on failure to forward the port. 66 Exception on failure to forward the port.
66 """ 67 """
67 with self._lock: 68 if not tool:
68 self._InitDeviceLocked(tool) 69 tool = valgrind_tools.CreateTool(None, adb)
69 host_name = '127.0.0.1' 70 with Forwarder._lock:
71 instance = Forwarder._GetInstanceLocked(build_type, tool)
72 instance._InitDeviceOnceLocked(adb, tool)
73
70 redirection_commands = [ 74 redirection_commands = [
71 ['--serial-id=' + self._adb.Adb().GetSerialNumber(), '--map', 75 ['--serial-id=' + adb.Adb().GetSerialNumber(), '--map',
72 str(device), str(host)] for device, host in port_pairs] 76 str(device), str(host)] for device, host in port_pairs]
73 logging.info('Forwarding using commands: %s', redirection_commands) 77 logging.info('Forwarding using commands: %s', redirection_commands)
74 78
75 for redirection_command in redirection_commands: 79 for redirection_command in redirection_commands:
76 try: 80 try:
77 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 81 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
78 [self._host_forwarder_path] + redirection_command) 82 [instance._host_forwarder_path] + redirection_command)
79 except OSError as e: 83 except OSError as e:
80 if e.errno == 2: 84 if e.errno == 2:
81 raise Exception('Unable to start host forwarder. Make sure you have' 85 raise Exception('Unable to start host forwarder. Make sure you have'
82 ' built host_forwarder.') 86 ' built host_forwarder.')
83 else: raise 87 else: raise
84 if exit_code != 0: 88 if exit_code != 0:
85 raise Exception('%s exited with %d:\n%s' % ( 89 raise Exception('%s exited with %d:\n%s' % (
86 self._host_forwarder_path, exit_code, '\n'.join(output))) 90 instance._host_forwarder_path, exit_code, '\n'.join(output)))
87 tokens = output.split(':') 91 tokens = output.split(':')
88 if len(tokens) != 2: 92 if len(tokens) != 2:
89 raise Exception(('Unexpected host forwarder output "%s", ' + 93 raise Exception(('Unexpected host forwarder output "%s", ' +
90 'expected "device_port:host_port"') % output) 94 'expected "device_port:host_port"') % output)
91 device_port = int(tokens[0]) 95 device_port = int(tokens[0])
92 host_port = int(tokens[1]) 96 host_port = int(tokens[1])
93 self._device_to_host_port_map[device_port] = host_port 97 instance._device_to_host_port_map[device_port] = host_port
94 self._host_to_device_port_map[host_port] = device_port 98 instance._host_to_device_port_map[host_port] = device_port
95 logging.info('Forwarding device port: %d to host port: %d.', 99 logging.info('Forwarding device port: %d to host port: %d.',
96 device_port, host_port) 100 device_port, host_port)
97 101
98 def _InitDeviceLocked(self, tool): 102 @staticmethod
99 """Initializes the device forwarder process (only once).""" 103 def UnmapDevicePort(device_port, adb):
100 if self._device_initialized: 104 """Unmaps a previously forwarded device port.
105
106 Args:
107 adb: An AndroidCommands instance.
108 device_port: A previously forwarded port (through Map()).
109 """
110 with Forwarder._lock:
111 instance = Forwarder._GetInstanceLocked(None, None)
112 if not device_port in instance._device_to_host_port_map:
113 return
114 redirection_command = [
115 '--serial-id=' + adb.Adb().GetSerialNumber(), '--unmap',
116 str(device_port)]
117 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
118 [instance._host_forwarder_path] + redirection_command)
119 if exit_code != 0:
120 logging.error('%s exited with %d:\n%s' % (
121 instance._host_forwarder_path, exit_code, '\n'.join(output)))
122 host_port = instance._device_to_host_port_map[device_port]
123 del instance._device_to_host_port_map[device_port]
124 del instance._host_to_device_port_map[host_port]
125
126 @staticmethod
127 def UnmapPorts(port_pairs, adb):
128 """Unmaps previously forwarded ports.
129
130 Args:
131 adb: An AndroidCommands instance.
132 port_pairs: A list of tuples (device_port, host_port) to unmap.
133 """
134 for (device_port, _) in port_pairs:
135 Forwarder.UnmapDevicePort(device_port, adb)
136
137 @staticmethod
138 def DevicePortForHostPort(host_port):
139 """Returns the device port that corresponds to a given host port."""
140 with Forwarder._lock:
141 return Forwarder._GetInstanceLocked(
142 None, None)._host_to_device_port_map.get(host_port)
143
144 @staticmethod
145 def _GetInstanceLocked(build_type, tool):
146 """Returns the singleton instance.
147
148 Note that the global lock must be acquired before calling this method.
149
150 Args:
151 build_type: 'Release' or 'Debug'
152 tool: Tool class to use to get wrapper, if necessary, for executing the
153 forwarder (see valgrind_tools.py).
154 """
155 if not Forwarder._instance:
156 Forwarder._instance = Forwarder(build_type, tool)
157 return Forwarder._instance
158
159 def __init__(self, build_type, tool):
160 """Constructs a new instance of Forwarder.
161
162 Note that Forwarder is a singleton therefore this constructor should be
163 called only once.
164
165 Args:
166 build_type: 'Release' or 'Debug'
167 tool: Tool class to use to get wrapper, if necessary, for executing the
168 forwarder (see valgrind_tools.py).
169 """
170 assert not Forwarder._instance
171 self._build_type = build_type
172 self._tool = tool
173 self._initialized_devices = set()
174 self._device_to_host_port_map = dict()
175 self._host_to_device_port_map = dict()
176 self._InitHostOnceLocked()
177 self._device_forwarder_path_on_host = os.path.join(
178 cmd_helper.OutDirectory.get(), self._build_type, 'forwarder_dist')
179
180 def _InitHostOnceLocked(self):
181 """Initializes the host forwarder daemon.
182
183 Note that the global lock must be acquired before calling this method. This
184 method determines the host_forwarder binary location and kills any existing
185 host_forwarder process that could be stale.
186 """
187 self._host_forwarder_path = _MakeBinaryPath(
188 self._build_type, 'host_forwarder')
189 if not os.path.exists(self._host_forwarder_path):
190 self._build_type = 'Release' if self._build_type == 'Debug' else 'Debug'
191 self._host_forwarder_path = _MakeBinaryPath(
192 self._build_type, 'host_forwarder')
193 assert os.path.exists(
194 self._host_forwarder_path), 'Please build forwarder2'
195 self._KillHostLocked()
196
197 def _InitDeviceOnceLocked(self, adb, tool):
198 """Initializes the device_forwarder daemon for a specific device (once).
199
200 Note that the global lock must be acquired before calling this method. This
201 method kills any existing device_forwarder daemon on the device that could
202 be stale, pushes the latest version of the daemon (to the device) and starts
203 it.
204
205 Args:
206 adb: An AndroidCommands instance.
207 tool: Tool class to use to get wrapper, if necessary, for executing the
208 forwarder (see valgrind_tools.py).
209 """
210 device_serial = adb.Adb().GetSerialNumber()
211 if device_serial in self._initialized_devices:
101 return 212 return
102 self._adb.PushIfNeeded( 213 Forwarder._KillDeviceLocked(adb, tool)
214 adb.PushIfNeeded(
103 self._device_forwarder_path_on_host, 215 self._device_forwarder_path_on_host,
104 Forwarder._DEVICE_FORWARDER_FOLDER) 216 Forwarder._DEVICE_FORWARDER_FOLDER)
105 (exit_code, output) = self._adb.GetShellCommandStatusAndOutput( 217 (exit_code, output) = adb.GetShellCommandStatusAndOutput(
106 '%s %s %s' % (Forwarder._LD_LIBRARY_PATH, tool.GetUtilWrapper(), 218 '%s %s %s' % (Forwarder._LD_LIBRARY_PATH, tool.GetUtilWrapper(),
107 Forwarder._DEVICE_FORWARDER_PATH)) 219 Forwarder._DEVICE_FORWARDER_PATH))
108 if exit_code != 0: 220 if exit_code != 0:
109 raise Exception( 221 raise Exception(
110 'Failed to start device forwarder:\n%s' % '\n'.join(output)) 222 'Failed to start device forwarder:\n%s' % '\n'.join(output))
111 self._device_initialized = True 223 self._initialized_devices.add(device_serial)
112 224
113 def UnmapDevicePort(self, device_port): 225 def _KillHostLocked(self):
114 """Unmaps a previously forwarded device port. 226 """Kills the forwarder process running on the host.
115 227
116 Args: 228 Note that the global lock must be acquired before calling this method.
117 device_port: A previously forwarded port (through Run()).
118 """ 229 """
119 with self._lock: 230 logging.info('Killing host_forwarder.')
120 self._UnmapDevicePortInternalLocked(device_port)
121
122 def _UnmapDevicePortInternalLocked(self, device_port):
123 if not device_port in self._device_to_host_port_map:
124 return
125 redirection_command = [
126 '--serial-id=' + self._adb.Adb().GetSerialNumber(), '--unmap',
127 str(device_port)]
128 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 231 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
129 [self._host_forwarder_path] + redirection_command) 232 [self._host_forwarder_path, '--kill-server'])
130 if exit_code != 0: 233 if exit_code != 0:
131 logging.error('%s exited with %d:\n%s' % ( 234 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
132 self._host_forwarder_path, exit_code, '\n'.join(output))) 235 ['pkill', '-9', 'host_forwarder'])
133 host_port = self._device_to_host_port_map[device_port] 236 if exit_code != 0:
134 del self._device_to_host_port_map[device_port] 237 raise Exception('%s exited with %d:\n%s' % (
135 del self._host_to_device_port_map[host_port] 238 self._host_forwarder_path, exit_code, '\n'.join(output)))
136 239
137 @staticmethod 240 @staticmethod
138 def KillHost(build_type='Debug'): 241 def _KillDeviceLocked(adb, tool):
139 """Kills the forwarder process running on the host. 242 """Kills the forwarder process running on the device.
140 243
141 Args: 244 Note that the global lock must be acquired before calling this method.
142 build_type: 'Release' or 'Debug' (default='Debug')
143 """
144 logging.info('Killing host_forwarder.')
145 host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
146 if not os.path.exists(host_forwarder_path):
147 host_forwarder_path = _MakeBinaryPath(
148 'Release' if build_type == 'Debug' else 'Debug', 'host_forwarder')
149 assert os.path.exists(host_forwarder_path), 'Please build forwarder2'
150 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
151 [host_forwarder_path, '--kill-server'])
152 if exit_code != 0:
153 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
154 ['pkill', 'host_forwarder'])
155 if exit_code != 0:
156 raise Exception('%s exited with %d:\n%s' % (
157 host_forwarder_path, exit_code, '\n'.join(output)))
158
159 @staticmethod
160 def KillDevice(adb, tool):
161 """Kills the forwarder process running on the device.
162 245
163 Args: 246 Args:
164 adb: Instance of AndroidCommands for talking to the device. 247 adb: Instance of AndroidCommands for talking to the device.
165 tool: Wrapper tool (e.g. valgrind) that can be used to execute the device 248 tool: Wrapper tool (e.g. valgrind) that can be used to execute the device
166 forwarder (see valgrind_tools.py). 249 forwarder (see valgrind_tools.py).
167 """ 250 """
168 logging.info('Killing device_forwarder.') 251 logging.info('Killing device_forwarder.')
169 if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH): 252 if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH):
170 return 253 return
171 (exit_code, output) = adb.GetShellCommandStatusAndOutput( 254 (exit_code, output) = adb.GetShellCommandStatusAndOutput(
172 '%s %s --kill-server' % (tool.GetUtilWrapper(), 255 '%s %s --kill-server' % (tool.GetUtilWrapper(),
173 Forwarder._DEVICE_FORWARDER_PATH)) 256 Forwarder._DEVICE_FORWARDER_PATH))
174 # TODO(pliard): Remove the following call to KillAllBlocking() when we are 257 # TODO(pliard): Remove the following call to KillAllBlocking() when we are
175 # sure that the old version of device_forwarder (not supporting 258 # sure that the old version of device_forwarder (not supporting
176 # 'kill-server') is not running on the bots anymore. 259 # 'kill-server') is not running on the bots anymore.
177 timeout_sec = 5 260 timeout_sec = 5
178 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec) 261 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec)
179 if not processes_killed: 262 if not processes_killed:
180 pids = adb.ExtractPid('device_forwarder') 263 pids = adb.ExtractPid('device_forwarder')
181 if pids: 264 if pids:
182 raise Exception('Timed out while killing device_forwarder') 265 raise Exception('Timed out while killing device_forwarder')
183
184 def DevicePortForHostPort(self, host_port):
185 """Returns the device port that corresponds to a given host port."""
186 with self._lock:
187 return self._host_to_device_port_map.get(host_port)
188
189 def Close(self):
190 """Releases the previously forwarded ports."""
191 with self._lock:
192 for device_port in self._device_to_host_port_map.copy():
193 self._UnmapDevicePortInternalLocked(device_port)
OLDNEW
« no previous file with comments | « build/android/pylib/chrome_test_server_spawner.py ('k') | build/android/pylib/instrumentation/test_runner.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698