diff options
Diffstat (limited to 'catapult/devil/devil/android/forwarder.py')
-rw-r--r-- | catapult/devil/devil/android/forwarder.py | 141 |
1 files changed, 88 insertions, 53 deletions
diff --git a/catapult/devil/devil/android/forwarder.py b/catapult/devil/devil/android/forwarder.py index 6be46516..cdb7e4d5 100644 --- a/catapult/devil/devil/android/forwarder.py +++ b/catapult/devil/devil/android/forwarder.py @@ -9,6 +9,8 @@ import inspect import logging import os import psutil +import re +import textwrap from devil import base_error from devil import devil_env @@ -25,6 +27,8 @@ logger = logging.getLogger(__name__) # Forwarder.DevicePortForHostPort. DYNAMIC_DEVICE_PORT = 0 +PORT_REGEX = re.compile(r'(?P<device_port>\d+):(?P<host_port>\d+)') + def _GetProcessStartTime(pid): p = psutil.Process(pid) @@ -34,7 +38,7 @@ def _GetProcessStartTime(pid): return p.create_time -def _LogMapFailureDiagnostics(device): +def _DumpHostLog(): # The host forwarder daemon logs to /tmp/host_forwarder_log, so print the end # of that. try: @@ -42,10 +46,14 @@ def _LogMapFailureDiagnostics(device): logger.info('Last 50 lines of the host forwarder daemon log:') for line in host_forwarder_log.read().splitlines()[-50:]: logger.info(' %s', line) - except Exception: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except # Grabbing the host forwarder log is best-effort. Ignore all errors. logger.warning('Failed to get the contents of host_forwarder_log.') + +def _LogMapFailureDiagnostics(device): + _DumpHostLog() + # The device forwarder daemon logs to the logcat, so print the end of that. try: logger.info('Last 50 lines of logcat:') @@ -101,10 +109,9 @@ class HostForwarderError(base_error.BaseError): class Forwarder(object): """Thread-safe class to manage port forwards from the device to the host.""" - _DEVICE_FORWARDER_FOLDER = (file_system.TEST_EXECUTABLE_DIR + - '/forwarder/') - _DEVICE_FORWARDER_PATH = (file_system.TEST_EXECUTABLE_DIR + - '/forwarder/device_forwarder') + _DEVICE_FORWARDER_FOLDER = (file_system.TEST_EXECUTABLE_DIR + '/forwarder/') + _DEVICE_FORWARDER_PATH = ( + file_system.TEST_EXECUTABLE_DIR + '/forwarder/device_forwarder') _LOCK_PATH = '/tmp/chrome.forwarder.lock' # Defined in host_forwarder_main.cc _HOST_FORWARDER_LOG = '/tmp/host_forwarder_log' @@ -137,11 +144,12 @@ class Forwarder(object): instance._InitDeviceLocked(device, tool) device_serial = str(device) - map_arg_lists = [ - ['--adb=' + adb_wrapper.AdbWrapper.GetAdbPath(), - '--serial-id=' + device_serial, - '--map', str(device_port), str(host_port)] - for device_port, host_port in port_pairs] + map_arg_lists = [[ + '--adb=' + adb_wrapper.AdbWrapper.GetAdbPath(), + '--serial-id=' + device_serial, '--map', + str(device_port), + str(host_port) + ] for device_port, host_port in port_pairs] logger.info('Forwarding using commands: %s', map_arg_lists) for map_arg_list in map_arg_lists: @@ -154,10 +162,10 @@ class Forwarder(object): '`%s` timed out:\n%s' % (' '.join(map_cmd), e.output)) except OSError as e: if e.errno == 2: - raise HostForwarderError( - 'Unable to start host forwarder. ' - 'Make sure you have built host_forwarder.') - else: raise + raise HostForwarderError('Unable to start host forwarder. ' + 'Make sure you have built host_forwarder.') + else: + raise if exit_code != 0: try: instance._KillDeviceLocked(device, tool) @@ -169,25 +177,25 @@ class Forwarder(object): 'Failed to kill the device forwarder after map failure: %s', str(e)) _LogMapFailureDiagnostics(device) - formatted_output = ('\n'.join(output) if isinstance(output, list) - else output) + formatted_output = ('\n'.join(output) + if isinstance(output, list) else output) raise HostForwarderError( - '`%s` exited with %d:\n%s' % ( - ' '.join(map_cmd), - exit_code, - formatted_output)) - tokens = output.split(':') - if len(tokens) != 2: - raise HostForwarderError( - 'Unexpected host forwarder output "%s", ' - 'expected "device_port:host_port"' % output) - device_port = int(tokens[0]) - host_port = int(tokens[1]) + '`%s` exited with %d:\n%s' % (' '.join(map_cmd), exit_code, + formatted_output)) + for line in output.splitlines(): + match = PORT_REGEX.match(line) + if match: + break + if not match: + raise HostForwarderError('Unable to find device_port:host_port in ' + 'host forwarder output: %s' % output) + device_port = int(match.groupdict()['device_port']) + host_port = int(match.groupdict()['host_port']) serial_with_port = (device_serial, device_port) instance._device_to_host_port_map[serial_with_port] = host_port instance._host_to_device_port_map[host_port] = serial_with_port - logger.info('Forwarding device port: %d to host port: %d.', - device_port, host_port) + logger.info('Forwarding device port: %d to host port: %d.', device_port, + host_port) @staticmethod def UnmapDevicePort(device_port, device): @@ -213,8 +221,7 @@ class Forwarder(object): unmap_all_cmd = [ instance._host_forwarder_path, '--adb=%s' % adb_wrapper.AdbWrapper.GetAdbPath(), - '--serial-id=%s' % device.serial, - '--unmap-all' + '--serial-id=%s' % device.serial, '--unmap-all' ] try: exit_code, output = cmd_helper.GetCmdStatusAndOutputWithTimeout( @@ -224,7 +231,8 @@ class Forwarder(object): '`%s` timed out:\n%s' % (' '.join(unmap_all_cmd), e.output)) if exit_code != 0: error_msg = [ - '`%s` exited with %d' % (' '.join(unmap_all_cmd), exit_code)] + '`%s` exited with %d' % (' '.join(unmap_all_cmd), exit_code) + ] if isinstance(output, list): error_msg += output else: @@ -317,8 +325,8 @@ class Forwarder(object): unmap_cmd = [ instance._host_forwarder_path, '--adb=%s' % adb_wrapper.AdbWrapper.GetAdbPath(), - '--serial-id=%s' % serial, - '--unmap', str(device_port) + '--serial-id=%s' % serial, '--unmap', + str(device_port) ] try: (exit_code, output) = cmd_helper.GetCmdStatusAndOutputWithTimeout( @@ -327,11 +335,8 @@ class Forwarder(object): raise HostForwarderError( '`%s` timed out:\n%s' % (' '.join(unmap_cmd), e.output)) if exit_code != 0: - logger.error( - '`%s` exited with %d:\n%s', - ' '.join(unmap_cmd), - exit_code, - '\n'.join(output) if isinstance(output, list) else output) + logger.error('`%s` exited with %d:\n%s', ' '.join(unmap_cmd), exit_code, + '\n'.join(output) if isinstance(output, list) else output) @staticmethod def _GetPidForLock(): @@ -393,18 +398,18 @@ class Forwarder(object): 'forwarder_device', device=device) forwarder_device_path_on_device = ( Forwarder._DEVICE_FORWARDER_FOLDER - if os.path.isdir(forwarder_device_path_on_host) - else Forwarder._DEVICE_FORWARDER_PATH) - device.PushChangedFiles([( - forwarder_device_path_on_host, - forwarder_device_path_on_device)]) + if os.path.isdir(forwarder_device_path_on_host) else + Forwarder._DEVICE_FORWARDER_PATH) + device.PushChangedFiles([(forwarder_device_path_on_host, + forwarder_device_path_on_device)]) cmd = [Forwarder._DEVICE_FORWARDER_PATH] wrapper = tool.GetUtilWrapper() if wrapper: cmd.insert(0, wrapper) device.RunShellCommand( - cmd, env={'LD_LIBRARY_PATH': Forwarder._DEVICE_FORWARDER_FOLDER}, + cmd, + env={'LD_LIBRARY_PATH': Forwarder._DEVICE_FORWARDER_FOLDER}, check_return=True) self._initialized_devices.add(device_serial) @@ -429,12 +434,41 @@ class Forwarder(object): kill_cmd = ['pkill', '-9', 'host_forwarder'] (exit_code, output) = cmd_helper.GetCmdStatusAndOutputWithTimeout( kill_cmd, Forwarder._TIMEOUT) - if exit_code != 0: - raise HostForwarderError( - '%s exited with %d:\n%s' % ( - self._host_forwarder_path, - exit_code, - '\n'.join(output) if isinstance(output, list) else output)) + if exit_code == -9: + # pkill can exit with -9, seemingly in cases where the process it's + # asked to kill dies sometime during pkill running. In this case, + # re-running should result in pkill succeeding. + logging.warning( + 'pkilling host forwarder returned -9, retrying. Output: %s', + output) + exit_code, output = cmd_helper.GetCmdStatusAndOutputWithTimeout( + kill_cmd, Forwarder._TIMEOUT) + if exit_code in (0, 1): + # pkill exits with a 0 if it was able to signal at least one process. + # pkill exits with a 1 if it wasn't able to singal a process because + # no matching process existed. We're ok with either. + return + + _, ps_output = cmd_helper.GetCmdStatusAndOutputWithTimeout( + ['ps', 'aux'], Forwarder._TIMEOUT) + host_forwarder_lines = [line for line in ps_output.splitlines() + if 'host_forwarder' in line] + if host_forwarder_lines: + logger.error('Remaining host_forwarder processes:\n %s', + '\n '.join(host_forwarder_lines)) + else: + logger.error('No remaining host_forwarder processes?') + _DumpHostLog() + error_msg = textwrap.dedent("""\ + `{kill_cmd}` failed to kill host_forwarder. + exit_code: {exit_code} + output: + {output} + """) + raise HostForwarderError( + error_msg.format( + kill_cmd=' '.join(kill_cmd), exit_code=str(exit_code), + output='\n'.join(' %s' % l for l in output.splitlines()))) except cmd_helper.TimeoutError as e: raise HostForwarderError( '`%s` timed out:\n%s' % (' '.join(kill_cmd), e.output)) @@ -472,5 +506,6 @@ class Forwarder(object): if wrapper: cmd.insert(0, wrapper) device.RunShellCommand( - cmd, env={'LD_LIBRARY_PATH': Forwarder._DEVICE_FORWARDER_FOLDER}, + cmd, + env={'LD_LIBRARY_PATH': Forwarder._DEVICE_FORWARDER_FOLDER}, check_return=True) |