| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A tool to kill any leftover test processes, executed by buildbot. |
| |
| Only works on Windows.""" |
| |
| import optparse |
| import os |
| import re |
| import subprocess |
| import sys |
| import time |
| |
| |
| def Log(message): |
| print '%s: %s' % (time.asctime(), message) |
| |
| |
| def KillAll(process_names, must_die=True): |
| """Tries to kill all copies of each process in the processes list.""" |
| killed_processes = [] |
| |
| Log('killing all processes in %r' % process_names) |
| for process_name in process_names: |
| if ProcessExists(process_name): |
| Kill(process_name) |
| killed_processes.append(process_name) |
| Log('process killed: %r' % killed_processes) |
| |
| # If we allow any processes to continue after trying to kill them, return |
| # now. |
| if not must_die: |
| return True |
| |
| # Give our processes time to exit. |
| for _ in range(60): |
| if not AnyProcessExists(killed_processes): |
| break |
| time.sleep(1) |
| |
| # We require that all processes we tried to kill must be killed. Let's |
| # verify that. |
| return not AnyProcessExists(killed_processes) |
| |
| |
| def ProcessExists(process_name): |
| """Return whether process_name is found in tasklist output.""" |
| # Use tasklist.exe to find if a given process_name is running. |
| Log('checking for process name %s' % process_name) |
| command = ('tasklist.exe /fi "imagename eq %s" | findstr.exe "K"' % |
| process_name) |
| # findstr.exe exits with code 0 if the given string is found. |
| return os.system(command) == 0 |
| |
| |
| def ProcessExistsByPid(pid): |
| """Return whether pid is found in tasklist output.""" |
| Log('checking for process id %d' % pid) |
| # Use tasklist.exe to find if a given process_name is running. |
| command = ('tasklist.exe /fi "pid eq %d" | findstr.exe "K"' % |
| pid) |
| # findstr.exe exits with code 0 if the given string is found. |
| return os.system(command) == 0 |
| |
| |
| def AnyProcessExists(process_list): |
| """Return whether any process from the list is still running.""" |
| return any(ProcessExists(process) for process in process_list) |
| |
| |
| def AnyProcessExistsByPid(pid_list): |
| """Return whether any process from the list is still running.""" |
| return any(ProcessExistsByPid(pid) for pid in pid_list) |
| |
| |
| def Kill(process_name): |
| command = ['taskkill.exe', '/f', '/t', '/im'] |
| subprocess.call(command + [process_name]) |
| |
| |
| def KillByPid(pid): |
| command = ['taskkill.exe', '/f', '/t', '/pid'] |
| subprocess.call(command + [str(pid)]) |
| |
| |
| def KillProcessesUsingCurrentDirectory(handle_exe): |
| if not os.path.exists(handle_exe): |
| return False |
| try: |
| Log('running %s to look for running processes' % handle_exe) |
| handle = subprocess.Popen([handle_exe, |
| os.path.join(os.getcwd(), 'src'), |
| '/accepteula'], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| except WindowsError, e: # pylint: disable=E0602 |
| print e |
| return False |
| stdout, stderr = handle.communicate() |
| |
| # Do a basic sanity check to make sure the tool is working fine. |
| if stderr or ('.exe' not in stdout and |
| 'Non-existant Process' not in stdout and |
| 'No matching handles found' not in stdout): |
| Log('Error running handle.exe: ' + repr((stdout, stderr))) |
| return False |
| |
| pid_list = [] |
| for line in stdout.splitlines(): # pylint: disable=E1103 |
| # Killing explorer.exe would hose the bot, don't do that. |
| if 'explorer.exe' in line: |
| continue |
| |
| if '.exe' in line: |
| match = re.match(r'.*pid: (\d+).*', line) |
| if match: |
| pid = int(match.group(1)) |
| |
| # Do not kill self. |
| if int(pid) == int(os.getpid()): |
| continue |
| |
| Log('Killing: ' + line) |
| pid_list.append(pid) |
| KillByPid(pid) |
| |
| # Give our processes time to exit. |
| for _ in range(60): |
| if not AnyProcessExistsByPid(pid_list): |
| break |
| time.sleep(1) |
| |
| return True |
| |
| |
| # rdpclip.exe is part of Remote Desktop. It has a bug that sometimes causes |
| # it to keep the clipboard open forever, denying other processes access to it. |
| # Killing BuildConsole.exe usually stops an IB build within a few seconds. |
| # Unfortunately, killing devenv.com or devenv.exe doesn't stop a VS build, so |
| # we don't bother pretending. |
| processes = [ |
| # Utilities we don't build, but which we use or otherwise can't |
| # have hanging around. |
| 'BuildConsole.exe', |
| 'httpd.exe', |
| 'lighttpd.exe', |
| 'outlook.exe', |
| 'perl.exe', |
| 'python_slave.exe', |
| 'rdpclip.exe', |
| 'svn.exe', |
| |
| # These processes are spawned by some tests and should be killed by same. |
| # It may occur that they are left dangling if a test crashes, so we kill |
| # them here too. |
| 'firefox.exe', |
| 'iexplore.exe', |
| #'ieuser.exe', |
| 'acrord32.exe', |
| 'pesq.exe', |
| 'PolqaOem64.exe', |
| |
| # The JIT debugger may start when devenv.exe crashes. |
| 'vsjitdebugger.exe', |
| # This process is also crashing once in a while during compile. |
| 'midlc.exe', |
| |
| # goma compiler proxy. |
| 'compiler_proxy.exe', |
| |
| # Things built by/for Chromium. |
| 'app_list_unittests.exe', |
| 'base_unittests.exe', |
| 'browser_tests.exe', |
| 'cacheinvalidation_unittests.exe', |
| 'chrome.exe', |
| 'chromedriver.exe', |
| 'chromedriver_tests.exe', |
| 'chromedriver_unittests.exe', |
| 'chrome_launcher.exe', |
| 'content_shell.exe', |
| 'content_shell_crash_service.exe', |
| 'content_unittests.exe', |
| 'crash_service.exe', |
| 'crypto_unittests.exe', |
| 'debug_message.exe', |
| 'device_unittests.exe', |
| 'DumpRenderTree.exe', |
| 'flush_cache.exe', |
| 'gl_tests.exe', |
| 'ie_unittests.exe', |
| 'image_diff.exe', |
| 'installer_util_unittests.exe', |
| 'interactive_ui_tests.exe', |
| 'ipc_tests.exe', |
| 'jingle_unittests.exe', |
| 'mediumtest_ie.exe', |
| 'memory_test.exe', |
| 'nacl64.exe', |
| 'net_unittests.exe', |
| 'page_cycler_tests.exe', |
| 'peerconnection_server.exe', |
| 'perf_tests.exe', |
| 'printing_unittests.exe', |
| 'sel_ldr.exe', |
| 'sel_ldr64.exe', |
| 'selenium_tests.exe', |
| 'startup_tests.exe', |
| 'sync_integration_tests.exe', |
| 'tab_switching_test.exe', |
| 'tld_cleanup.exe', |
| 'unit_tests.exe', |
| 'v8_shell.exe', |
| 'v8_mksnapshot.exe', |
| 'v8_shell_sample.exe', |
| 'wow_helper.exe', |
| ] |
| |
| # Some processes may be present occasionally unrelated to the current build. |
| # For these, it's not an error if we attempt to kill them and they don't go |
| # away. |
| lingering_processes = [ |
| # When VC crashes during compilation, this process which manages the .pdb |
| # file generation sometime hangs. However, Incredibuild will spawn |
| # mspdbsrv.exe, so don't trigger an error if it is still present after |
| # we attempt to kill it. |
| 'mspdbsrv.exe', |
| ] |
| |
| def main(): |
| handle_exe_default = os.path.join(os.getcwd(), '..', '..', '..', |
| 'third_party', 'psutils', 'handle.exe') |
| |
| parser = optparse.OptionParser( |
| usage='%prog [options]') |
| parser.add_option('--handle_exe', default=handle_exe_default, |
| help='The path to handle.exe. Defaults to %default.') |
| (options, args) = parser.parse_args() |
| |
| if args: |
| parser.error('Unknown arguments passed in, %s' % args) |
| |
| # Kill all lingering processes. It's okay if these aren't killed or end up |
| # reappearing. |
| KillAll(lingering_processes, must_die=False) |
| KillProcessesUsingCurrentDirectory(options.handle_exe) |
| |
| # Kill all regular processes. We must guarantee that these are killed since |
| # we exit with an error code if they're not. |
| if KillAll(processes, must_die=True): |
| return 0 |
| |
| # Some processes were not killed, exit with non-zero status. |
| return 1 |
| |
| |
| if '__main__' == __name__: |
| sys.exit(main()) |