1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li 7*9c5db199SXin Li''' 8*9c5db199SXin LiA library to prespawn autotest processes to minimize startup overhead. 9*9c5db199SXin Li''' 10*9c5db199SXin Li 11*9c5db199SXin Liimport six.moves.cPickle as pickle, os, sys 12*9c5db199SXin Lifrom setproctitle import setproctitle 13*9c5db199SXin Li 14*9c5db199SXin Li 15*9c5db199SXin Liif len(sys.argv) == 2 and sys.argv[1] == '--prespawn_autotest': 16*9c5db199SXin Li # Run an autotest process, and on stdin, wait for a pickled environment + 17*9c5db199SXin Li # argv (as a tuple); see spawn() below. Once we receive these, start 18*9c5db199SXin Li # autotest. 19*9c5db199SXin Li 20*9c5db199SXin Li # Do common imports (to save startup time). 21*9c5db199SXin Li # pylint: disable=W0611 22*9c5db199SXin Li import common 23*9c5db199SXin Li import autotest_lib.client.bin.job 24*9c5db199SXin Li from autotest_lib.client.common_lib import seven 25*9c5db199SXin Li 26*9c5db199SXin Li if os.environ.get('CROS_DISABLE_SITE_SYSINFO'): 27*9c5db199SXin Li from autotest_lib.client.bin import sysinfo, base_sysinfo 28*9c5db199SXin Li sysinfo.sysinfo = autotest_lib.client.bin.base_sysinfo.base_sysinfo 29*9c5db199SXin Li 30*9c5db199SXin Li # Wait for environment and autotest arguments. 31*9c5db199SXin Li env, sys.argv = pickle.load(sys.stdin) 32*9c5db199SXin Li # Run autotest and exit. 33*9c5db199SXin Li if env: 34*9c5db199SXin Li os.environ.clear() 35*9c5db199SXin Li os.environ.update(env) 36*9c5db199SXin Li proc_title = os.environ.get('CROS_PROC_TITLE') 37*9c5db199SXin Li if proc_title: 38*9c5db199SXin Li setproctitle(proc_title) 39*9c5db199SXin Li 40*9c5db199SXin Li seven.exec_file('autotest', {}, {}) 41*9c5db199SXin Li sys.exit(0) 42*9c5db199SXin Li 43*9c5db199SXin Li 44*9c5db199SXin Liimport logging, subprocess, threading 45*9c5db199SXin Lifrom six.moves.queue import Queue 46*9c5db199SXin Li 47*9c5db199SXin Li 48*9c5db199SXin LiNUM_PRESPAWNED_PROCESSES = 1 49*9c5db199SXin Li 50*9c5db199SXin Li 51*9c5db199SXin Liclass Prespawner(): 52*9c5db199SXin Li def __init__(self): 53*9c5db199SXin Li self.prespawned = Queue(NUM_PRESPAWNED_PROCESSES) 54*9c5db199SXin Li self.thread = None 55*9c5db199SXin Li self.terminated = False 56*9c5db199SXin Li 57*9c5db199SXin Li def spawn(self, args, env_additions=None): 58*9c5db199SXin Li ''' 59*9c5db199SXin Li Spawns a new autotest (reusing an prespawned process if available). 60*9c5db199SXin Li 61*9c5db199SXin Li @param args: A list of arguments (sys.argv) 62*9c5db199SXin Li @param env_additions: Items to add to the current environment 63*9c5db199SXin Li ''' 64*9c5db199SXin Li new_env = dict(os.environ) 65*9c5db199SXin Li if env_additions: 66*9c5db199SXin Li new_env.update(env_additions) 67*9c5db199SXin Li 68*9c5db199SXin Li process = self.prespawned.get() 69*9c5db199SXin Li # Write the environment and argv to the process's stdin; it will launch 70*9c5db199SXin Li # autotest once these are received. 71*9c5db199SXin Li pickle.dump((new_env, args), process.stdin, protocol=2) 72*9c5db199SXin Li process.stdin.close() 73*9c5db199SXin Li return process 74*9c5db199SXin Li 75*9c5db199SXin Li def start(self): 76*9c5db199SXin Li ''' 77*9c5db199SXin Li Starts a thread to pre-spawn autotests. 78*9c5db199SXin Li ''' 79*9c5db199SXin Li def run(): 80*9c5db199SXin Li while not self.terminated: 81*9c5db199SXin Li process = subprocess.Popen( 82*9c5db199SXin Li ['python', '-u', os.path.realpath(__file__), 83*9c5db199SXin Li '--prespawn_autotest'], 84*9c5db199SXin Li cwd=os.path.dirname(os.path.realpath(__file__)), 85*9c5db199SXin Li stdin=subprocess.PIPE) 86*9c5db199SXin Li logging.debug('Pre-spawned an autotest process %d', process.pid) 87*9c5db199SXin Li self.prespawned.put(process) 88*9c5db199SXin Li 89*9c5db199SXin Li # Let stop() know that we are done 90*9c5db199SXin Li self.prespawned.put(None) 91*9c5db199SXin Li 92*9c5db199SXin Li if not self.thread: 93*9c5db199SXin Li self.thread = threading.Thread(target=run, name='Prespawner') 94*9c5db199SXin Li self.thread.start() 95*9c5db199SXin Li 96*9c5db199SXin Li def stop(self): 97*9c5db199SXin Li ''' 98*9c5db199SXin Li Stops the pre-spawn thread gracefully. 99*9c5db199SXin Li ''' 100*9c5db199SXin Li if not self.thread: 101*9c5db199SXin Li # Never started 102*9c5db199SXin Li return 103*9c5db199SXin Li 104*9c5db199SXin Li self.terminated = True 105*9c5db199SXin Li # Wait for any existing prespawned processes. 106*9c5db199SXin Li while True: 107*9c5db199SXin Li process = self.prespawned.get() 108*9c5db199SXin Li if not process: 109*9c5db199SXin Li break 110*9c5db199SXin Li # Send a 'None' environment and arg list to tell the prespawner 111*9c5db199SXin Li # processes to exit. 112*9c5db199SXin Li pickle.dump((None, None), process.stdin, protocol=2) 113*9c5db199SXin Li process.stdin.close() 114*9c5db199SXin Li process.wait() 115*9c5db199SXin Li self.thread = None 116