# Copyright (c) 2012 The Chromium OS 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 library to prespawn autotest processes to minimize startup overhead. ''' import cPickle as pickle, os, sys from setproctitle import setproctitle if len(sys.argv) == 2 and sys.argv[1] == '--prespawn_autotest': # Run an autotest process, and on stdin, wait for a pickled environment + # argv (as a tuple); see spawn() below. Once we receive these, start # autotest. # Do common imports (to save startup time). # pylint: disable=W0611 import common import autotest_lib.client.bin.job if os.environ.get('CROS_DISABLE_SITE_SYSINFO'): from autotest_lib.client.bin import sysinfo, base_sysinfo sysinfo.sysinfo = autotest_lib.client.bin.base_sysinfo.base_sysinfo # Wait for environment and autotest arguments. env, sys.argv = pickle.load(sys.stdin) # Run autotest and exit. if env: os.environ.clear() os.environ.update(env) proc_title = os.environ.get('CROS_PROC_TITLE') if proc_title: setproctitle(proc_title) execfile('autotest') sys.exit(0) import logging, subprocess, threading from Queue import Queue NUM_PRESPAWNED_PROCESSES = 1 class Prespawner(): def __init__(self): self.prespawned = Queue(NUM_PRESPAWNED_PROCESSES) self.thread = None self.terminated = False def spawn(self, args, env_additions=None): ''' Spawns a new autotest (reusing an prespawned process if available). @param args: A list of arguments (sys.argv) @param env_additions: Items to add to the current environment ''' new_env = dict(os.environ) if env_additions: new_env.update(env_additions) process = self.prespawned.get() # Write the environment and argv to the process's stdin; it will launch # autotest once these are received. pickle.dump((new_env, args), process.stdin, protocol=2) process.stdin.close() return process def start(self): ''' Starts a thread to pre-spawn autotests. ''' def run(): while not self.terminated: process = subprocess.Popen( ['python', '-u', os.path.realpath(__file__), '--prespawn_autotest'], cwd=os.path.dirname(os.path.realpath(__file__)), stdin=subprocess.PIPE) logging.debug('Pre-spawned an autotest process %d', process.pid) self.prespawned.put(process) # Let stop() know that we are done self.prespawned.put(None) if not self.thread: self.thread = threading.Thread(target=run, name='Prespawner') self.thread.start() def stop(self): ''' Stops the pre-spawn thread gracefully. ''' if not self.thread: # Never started return self.terminated = True # Wait for any existing prespawned processes. while True: process = self.prespawned.get() if not process: break # Send a 'None' environment and arg list to tell the prespawner # processes to exit. pickle.dump((None, None), process.stdin, protocol=2) process.stdin.close() process.wait() self.thread = None