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

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

Issue 19844006: Revert "Reland r212020: Move Python setup/tear down logic into Forwarder ..." (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase 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
6 import logging 5 import logging
7 import os 6 import os
8 import psutil
9 import re 7 import re
10 import sys 8 import sys
9 import threading
11 import time 10 import time
12 11
13 import android_commands 12 import android_commands
14 import cmd_helper 13 import cmd_helper
15 import constants 14 import constants
16 15
17 from pylib import valgrind_tools 16 from pylib import pexpect
18 17
19 18
20 def _MakeBinaryPath(build_type, binary_name): 19 def _MakeBinaryPath(build_type, binary_name):
21 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)
22 21
23 22
24 def _GetProcessStartTime(pid):
25 return psutil.Process(pid).create_time
26
27
28 class _FileLock(object):
29 """With statement-aware implementation of a file lock.
30
31 File locks are needed for cross-process synchronization when the
32 multiprocessing Python module is used.
33 """
34 def __init__(self, path):
35 self._path = path
36
37 def __enter__(self):
38 self._fd = os.open(self._path, os.O_RDONLY | os.O_CREAT)
39 if self._fd < 0:
40 raise Exception('Could not open file %s for reading' % self._path)
41 fcntl.flock(self._fd, fcntl.LOCK_EX)
42
43 def __exit__(self, type, value, traceback):
44 fcntl.flock(self._fd, fcntl.LOCK_UN)
45 os.close(self._fd)
46
47
48 class Forwarder(object): 23 class Forwarder(object):
49 """Thread-safe class to manage port forwards from the device to the host.""" 24 """Thread-safe class to manage port forwards from the device to the host."""
50 25
51 _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR + 26 _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR +
52 '/forwarder/') 27 '/forwarder/')
53 _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR + 28 _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR +
54 '/forwarder/device_forwarder') 29 '/forwarder/device_forwarder')
55 _LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % _DEVICE_FORWARDER_FOLDER 30 _LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % _DEVICE_FORWARDER_FOLDER
56 _LOCK_PATH = '/tmp/chrome.forwarder.lock'
57 _MULTIPROCESSING_ENV_VAR = 'CHROME_FORWARDER_USE_MULTIPROCESSING'
58 31
59 _instance = None 32 def __init__(self, adb, build_type):
33 """Forwards TCP ports on the device back to the host.
60 34
61 @staticmethod 35 Works like adb forward, but in reverse.
62 def UseMultiprocessing():
63 """Tells the forwarder that multiprocessing is used."""
64 os.environ[Forwarder._MULTIPROCESSING_ENV_VAR] = '1'
65 36
66 @staticmethod 37 Args:
67 def Map(port_pairs, adb, build_type='Debug', tool=None): 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):
68 """Runs the forwarder. 53 """Runs the forwarder.
69 54
70 Args: 55 Args:
71 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
72 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
73 port will by dynamically assigned on the device. You can 58 port will by dynamically assigned on the device. You can
74 get the number of the assigned port using the 59 get the number of the assigned port using the
75 DevicePortForHostPort method. 60 DevicePortForHostPort method.
76 adb: An AndroidCommands instance.
77 tool: Tool class to use to get wrapper, if necessary, for executing the 61 tool: Tool class to use to get wrapper, if necessary, for executing the
78 forwarder (see valgrind_tools.py). 62 forwarder (see valgrind_tools.py).
79 63
80 Raises: 64 Raises:
81 Exception on failure to forward the port. 65 Exception on failure to forward the port.
82 """ 66 """
83 if not tool: 67 with self._lock:
84 tool = valgrind_tools.CreateTool(None, adb) 68 self._InitDeviceLocked(tool)
85 with _FileLock(Forwarder._LOCK_PATH): 69 host_name = '127.0.0.1'
86 instance = Forwarder._GetInstanceLocked(build_type, tool)
87 instance._InitDeviceLocked(adb, tool)
88
89 device_serial = adb.Adb().GetSerialNumber()
90 redirection_commands = [ 70 redirection_commands = [
91 ['--serial-id=' + device_serial, '--map', str(device), 71 ['--serial-id=' + self._adb.Adb().GetSerialNumber(), '--map',
92 str(host)] for device, host in port_pairs] 72 str(device), str(host)] for device, host in port_pairs]
93 logging.info('Forwarding using commands: %s', redirection_commands) 73 logging.info('Forwarding using commands: %s', redirection_commands)
94 74
95 for redirection_command in redirection_commands: 75 for redirection_command in redirection_commands:
96 try: 76 try:
97 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 77 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
98 [instance._host_forwarder_path] + redirection_command) 78 [self._host_forwarder_path] + redirection_command)
99 except OSError as e: 79 except OSError as e:
100 if e.errno == 2: 80 if e.errno == 2:
101 raise Exception('Unable to start host forwarder. Make sure you have' 81 raise Exception('Unable to start host forwarder. Make sure you have'
102 ' built host_forwarder.') 82 ' built host_forwarder.')
103 else: raise 83 else: raise
104 if exit_code != 0: 84 if exit_code != 0:
105 raise Exception('%s exited with %d:\n%s' % ( 85 raise Exception('%s exited with %d:\n%s' % (
106 instance._host_forwarder_path, exit_code, '\n'.join(output))) 86 self._host_forwarder_path, exit_code, '\n'.join(output)))
107 tokens = output.split(':') 87 tokens = output.split(':')
108 if len(tokens) != 2: 88 if len(tokens) != 2:
109 raise Exception(('Unexpected host forwarder output "%s", ' + 89 raise Exception(('Unexpected host forwarder output "%s", ' +
110 'expected "device_port:host_port"') % output) 90 'expected "device_port:host_port"') % output)
111 device_port = int(tokens[0]) 91 device_port = int(tokens[0])
112 host_port = int(tokens[1]) 92 host_port = int(tokens[1])
113 serial_with_port = (device_serial, device_port) 93 self._device_to_host_port_map[device_port] = host_port
114 instance._device_to_host_port_map[serial_with_port] = host_port 94 self._host_to_device_port_map[host_port] = device_port
115 instance._host_to_device_port_map[host_port] = serial_with_port
116 logging.info('Forwarding device port: %d to host port: %d.', 95 logging.info('Forwarding device port: %d to host port: %d.',
117 device_port, host_port) 96 device_port, host_port)
118 97
119 @staticmethod 98 def _InitDeviceLocked(self, tool):
120 def UnmapDevicePort(device_port, adb): 99 """Initializes the device forwarder process (only once)."""
121 """Unmaps a previously forwarded device port. 100 if self._device_initialized:
122
123 Args:
124 adb: An AndroidCommands instance.
125 device_port: A previously forwarded port (through Map()).
126 """
127 with _FileLock(Forwarder._LOCK_PATH):
128 Forwarder._UnmapDevicePortLocked(device_port, adb)
129
130 @staticmethod
131 def UnmapAllDevicePorts(adb):
132 """Unmaps all the previously forwarded ports for the provided device.
133
134 Args:
135 adb: An AndroidCommands instance.
136 port_pairs: A list of tuples (device_port, host_port) to unmap.
137 """
138 with _FileLock(Forwarder._LOCK_PATH):
139 port_map = Forwarder._GetInstanceLocked(
140 None, None)._device_to_host_port_map
141 adb_serial = adb.Adb().GetSerialNumber()
142 for ((device_serial, device_port), _) in port_map:
143 if adb_serial == device_serial:
144 Forwarder._UnmapDevicePortLocked(device_port, adb)
145
146 @staticmethod
147 def DevicePortForHostPort(host_port):
148 """Returns the device port that corresponds to a given host port."""
149 with _FileLock(Forwarder._LOCK_PATH):
150 (device_serial, device_port) = Forwarder._GetInstanceLocked(
151 None, None)._host_to_device_port_map.get(host_port)
152 return device_port
153
154 @staticmethod
155 def _GetInstanceLocked(build_type, tool):
156 """Returns the singleton instance.
157
158 Note that the global lock must be acquired before calling this method.
159
160 Args:
161 build_type: 'Release' or 'Debug'
162 tool: Tool class to use to get wrapper, if necessary, for executing the
163 forwarder (see valgrind_tools.py).
164 """
165 if not Forwarder._instance:
166 Forwarder._instance = Forwarder(build_type, tool)
167 return Forwarder._instance
168
169 def __init__(self, build_type, tool):
170 """Constructs a new instance of Forwarder.
171
172 Note that Forwarder is a singleton therefore this constructor should be
173 called only once.
174
175 Args:
176 build_type: 'Release' or 'Debug'
177 tool: Tool class to use to get wrapper, if necessary, for executing the
178 forwarder (see valgrind_tools.py).
179 """
180 assert not Forwarder._instance
181 self._build_type = build_type
182 self._tool = tool
183 self._initialized_devices = set()
184 self._device_to_host_port_map = dict()
185 self._host_to_device_port_map = dict()
186 self._host_forwarder_path = _MakeBinaryPath(
187 self._build_type, 'host_forwarder')
188 if not os.path.exists(self._host_forwarder_path):
189 self._build_type = 'Release' if self._build_type == 'Debug' else 'Debug'
190 self._host_forwarder_path = _MakeBinaryPath(
191 self._build_type, 'host_forwarder')
192 assert os.path.exists(
193 self._host_forwarder_path), 'Please build forwarder2'
194 self._device_forwarder_path_on_host = os.path.join(
195 cmd_helper.OutDirectory.get(), self._build_type, 'forwarder_dist')
196 self._InitHostLocked()
197
198 @staticmethod
199 def _UnmapDevicePortLocked(device_port, adb):
200 """Internal method used by UnmapDevicePort().
201
202 Note that the global lock must be acquired before calling this method.
203 """
204 instance = Forwarder._GetInstanceLocked(None, None)
205 serial = adb.Adb().GetSerialNumber()
206 serial_with_port = (serial, device_port)
207 if not serial_with_port in instance._device_to_host_port_map:
208 logging.error('Trying to unmap non-forwarded port %d' % device_port)
209 return 101 return
210 redirection_command = ['--serial-id=' + serial, '--unmap', str(device_port)] 102 self._adb.PushIfNeeded(
211 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
212 [instance._host_forwarder_path] + redirection_command)
213 if exit_code != 0:
214 logging.error('%s exited with %d:\n%s' % (
215 instance._host_forwarder_path, exit_code, '\n'.join(output)))
216 host_port = instance._device_to_host_port_map[serial_with_port]
217 del instance._device_to_host_port_map[serial_with_port]
218 del instance._host_to_device_port_map[host_port]
219
220 @staticmethod
221 def _GetPidForLock():
222 """Returns the PID used for host_forwarder initialization.
223
224 In case multi-process sharding is used, the PID of the "sharder" is used.
225 The "sharder" is the initial process that forks that is the parent process.
226 By default, multi-processing is not used. In that case the PID of the
227 current process is returned.
228 """
229 use_multiprocessing = Forwarder._MULTIPROCESSING_ENV_VAR in os.environ
230 return os.getppid() if use_multiprocessing else os.getpid()
231
232 def _InitHostLocked(self):
233 """Initializes the host forwarder daemon.
234
235 Note that the global lock must be acquired before calling this method. This
236 method kills any existing host_forwarder process that could be stale.
237 """
238 # See if the host_forwarder daemon was already initialized by a concurrent
239 # process or thread (in case multi-process sharding is not used).
240 pid_for_lock = Forwarder._GetPidForLock()
241 fd = os.open(Forwarder._LOCK_PATH, os.O_RDWR | os.O_CREAT)
242 with os.fdopen(fd, 'r+') as pid_file:
243 pid_with_start_time = pid_file.readline()
244 if pid_with_start_time:
245 (pid, process_start_time) = pid_with_start_time.split(':')
246 if pid == str(pid_for_lock):
247 if process_start_time == str(_GetProcessStartTime(pid_for_lock)):
248 return
249 self._KillHostLocked()
250 pid_file.seek(0)
251 pid_file.write(
252 '%s:%s' % (pid_for_lock, str(_GetProcessStartTime(pid_for_lock))))
253
254 def _InitDeviceLocked(self, adb, tool):
255 """Initializes the device_forwarder daemon for a specific device (once).
256
257 Note that the global lock must be acquired before calling this method. This
258 method kills any existing device_forwarder daemon on the device that could
259 be stale, pushes the latest version of the daemon (to the device) and starts
260 it.
261
262 Args:
263 adb: An AndroidCommands instance.
264 tool: Tool class to use to get wrapper, if necessary, for executing the
265 forwarder (see valgrind_tools.py).
266 """
267 device_serial = adb.Adb().GetSerialNumber()
268 if device_serial in self._initialized_devices:
269 return
270 Forwarder._KillDeviceLocked(adb, tool)
271 adb.PushIfNeeded(
272 self._device_forwarder_path_on_host, 103 self._device_forwarder_path_on_host,
273 Forwarder._DEVICE_FORWARDER_FOLDER) 104 Forwarder._DEVICE_FORWARDER_FOLDER)
274 (exit_code, output) = adb.GetShellCommandStatusAndOutput( 105 (exit_code, output) = self._adb.GetShellCommandStatusAndOutput(
275 '%s %s %s' % (Forwarder._LD_LIBRARY_PATH, tool.GetUtilWrapper(), 106 '%s %s %s' % (Forwarder._LD_LIBRARY_PATH, tool.GetUtilWrapper(),
276 Forwarder._DEVICE_FORWARDER_PATH)) 107 Forwarder._DEVICE_FORWARDER_PATH))
277 if exit_code != 0: 108 if exit_code != 0:
278 raise Exception( 109 raise Exception(
279 'Failed to start device forwarder:\n%s' % '\n'.join(output)) 110 'Failed to start device forwarder:\n%s' % '\n'.join(output))
280 self._initialized_devices.add(device_serial) 111 self._device_initialized = True
281 112
282 def _KillHostLocked(self): 113 def UnmapDevicePort(self, device_port):
114 """Unmaps a previously forwarded device port.
115
116 Args:
117 device_port: A previously forwarded port (through Run()).
118 """
119 with self._lock:
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(
129 [self._host_forwarder_path] + redirection_command)
130 if exit_code != 0:
131 logging.error('%s exited with %d:\n%s' % (
132 self._host_forwarder_path, exit_code, '\n'.join(output)))
133 host_port = self._device_to_host_port_map[device_port]
134 del self._device_to_host_port_map[device_port]
135 del self._host_to_device_port_map[host_port]
136
137 @staticmethod
138 def KillHost(build_type='Debug'):
283 """Kills the forwarder process running on the host. 139 """Kills the forwarder process running on the host.
284 140
285 Note that the global lock must be acquired before calling this method. 141 Args:
142 build_type: 'Release' or 'Debug' (default='Debug')
286 """ 143 """
287 logging.info('Killing host_forwarder.') 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'
288 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 150 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
289 [self._host_forwarder_path, '--kill-server']) 151 [host_forwarder_path, '--kill-server'])
290 if exit_code != 0: 152 if exit_code != 0:
291 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 153 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
292 ['pkill', '-9', 'host_forwarder']) 154 ['pkill', 'host_forwarder'])
293 if exit_code != 0: 155 if exit_code != 0:
294 raise Exception('%s exited with %d:\n%s' % ( 156 raise Exception('%s exited with %d:\n%s' % (
295 self._host_forwarder_path, exit_code, '\n'.join(output))) 157 host_forwarder_path, exit_code, '\n'.join(output)))
296 158
297 @staticmethod 159 @staticmethod
298 def _KillDeviceLocked(adb, tool): 160 def KillDevice(adb, tool):
299 """Kills the forwarder process running on the device. 161 """Kills the forwarder process running on the device.
300 162
301 Note that the global lock must be acquired before calling this method.
302
303 Args: 163 Args:
304 adb: Instance of AndroidCommands for talking to the device. 164 adb: Instance of AndroidCommands for talking to the device.
305 tool: Wrapper tool (e.g. valgrind) that can be used to execute the device 165 tool: Wrapper tool (e.g. valgrind) that can be used to execute the device
306 forwarder (see valgrind_tools.py). 166 forwarder (see valgrind_tools.py).
307 """ 167 """
308 logging.info('Killing device_forwarder.') 168 logging.info('Killing device_forwarder.')
309 if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH): 169 if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH):
310 return 170 return
311 (exit_code, output) = adb.GetShellCommandStatusAndOutput( 171 (exit_code, output) = adb.GetShellCommandStatusAndOutput(
312 '%s %s --kill-server' % (tool.GetUtilWrapper(), 172 '%s %s --kill-server' % (tool.GetUtilWrapper(),
313 Forwarder._DEVICE_FORWARDER_PATH)) 173 Forwarder._DEVICE_FORWARDER_PATH))
314 # TODO(pliard): Remove the following call to KillAllBlocking() when we are 174 # TODO(pliard): Remove the following call to KillAllBlocking() when we are
315 # sure that the old version of device_forwarder (not supporting 175 # sure that the old version of device_forwarder (not supporting
316 # 'kill-server') is not running on the bots anymore. 176 # 'kill-server') is not running on the bots anymore.
317 timeout_sec = 5 177 timeout_sec = 5
318 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec) 178 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec)
319 if not processes_killed: 179 if not processes_killed:
320 pids = adb.ExtractPid('device_forwarder') 180 pids = adb.ExtractPid('device_forwarder')
321 if pids: 181 if pids:
322 raise Exception('Timed out while killing device_forwarder') 182 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/host_driven/python_test_sharder.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698