| # 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 |