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