xref: /aosp_15_r20/external/autotest/autotest_lib/client/bin/prespawner.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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