xref: /aosp_15_r20/external/autotest/autotest_lib/client/bin/parallel.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li""" Parallel execution management """
3*9c5db199SXin Li
4*9c5db199SXin Li__author__ = """Copyright Andy Whitcroft 2006"""
5*9c5db199SXin Li
6*9c5db199SXin Liimport gc
7*9c5db199SXin Liimport logging
8*9c5db199SXin Liimport os
9*9c5db199SXin Liimport pickle
10*9c5db199SXin Liimport six
11*9c5db199SXin Liimport sys
12*9c5db199SXin Liimport time
13*9c5db199SXin Liimport traceback
14*9c5db199SXin Li
15*9c5db199SXin Lifrom autotest_lib.client.common_lib import error, utils
16*9c5db199SXin Li
17*9c5db199SXin Lidef fork_start(tmp, l):
18*9c5db199SXin Li    sys.stdout.flush()
19*9c5db199SXin Li    sys.stderr.flush()
20*9c5db199SXin Li    pid = os.fork()
21*9c5db199SXin Li    if pid:
22*9c5db199SXin Li        # Parent
23*9c5db199SXin Li        return pid
24*9c5db199SXin Li
25*9c5db199SXin Li    try:
26*9c5db199SXin Li        try:
27*9c5db199SXin Li            l()
28*9c5db199SXin Li        except error.AutotestError:
29*9c5db199SXin Li            raise
30*9c5db199SXin Li        except Exception as e:
31*9c5db199SXin Li            raise error.UnhandledTestError(e)
32*9c5db199SXin Li    except Exception as detail:
33*9c5db199SXin Li        try:
34*9c5db199SXin Li            try:
35*9c5db199SXin Li                logging.error('child process failed')
36*9c5db199SXin Li                # logging.exception() uses ERROR level, but we want DEBUG for
37*9c5db199SXin Li                # the traceback
38*9c5db199SXin Li                for line in traceback.format_exc().splitlines():
39*9c5db199SXin Li                    logging.debug(line)
40*9c5db199SXin Li            finally:
41*9c5db199SXin Li                # note that exceptions originating in this block won't make it
42*9c5db199SXin Li                # to the logs
43*9c5db199SXin Li                output_dir = os.path.join(tmp, 'debug')
44*9c5db199SXin Li                if not os.path.exists(output_dir):
45*9c5db199SXin Li                    os.makedirs(output_dir)
46*9c5db199SXin Li                ename = os.path.join(output_dir, "error-%d" % os.getpid())
47*9c5db199SXin Li
48*9c5db199SXin Li                # Python 3+ requires binary mode.
49*9c5db199SXin Li                mode = 'w' if six.PY2 else 'wb'
50*9c5db199SXin Li                with open(ename, mode) as pickle_out:
51*9c5db199SXin Li                    pickle.dump(detail, pickle_out)
52*9c5db199SXin Li
53*9c5db199SXin Li                sys.stdout.flush()
54*9c5db199SXin Li                sys.stderr.flush()
55*9c5db199SXin Li        finally:
56*9c5db199SXin Li            # clear exception information to allow garbage collection of
57*9c5db199SXin Li            # objects referenced by the exception's traceback
58*9c5db199SXin Li            # exc_clear() doesn't exist in py3 (nor is needed).
59*9c5db199SXin Li            if six.PY2:
60*9c5db199SXin Li                sys.exc_clear()
61*9c5db199SXin Li            gc.collect()
62*9c5db199SXin Li            os._exit(1)
63*9c5db199SXin Li    else:
64*9c5db199SXin Li        try:
65*9c5db199SXin Li            sys.stdout.flush()
66*9c5db199SXin Li            sys.stderr.flush()
67*9c5db199SXin Li        finally:
68*9c5db199SXin Li            os._exit(0)
69*9c5db199SXin Li
70*9c5db199SXin Li
71*9c5db199SXin Lidef _check_for_subprocess_exception(temp_dir, pid):
72*9c5db199SXin Li    ename = temp_dir + "/debug/error-%d" % pid
73*9c5db199SXin Li    if os.path.exists(ename):
74*9c5db199SXin Li        try:
75*9c5db199SXin Li            # Python 3+ requires binary mode.
76*9c5db199SXin Li            mode = 'r' if six.PY2 else 'rb'
77*9c5db199SXin Li            with open(ename, mode) as rf:
78*9c5db199SXin Li                e = pickle.load(rf)
79*9c5db199SXin Li        except ImportError:
80*9c5db199SXin Li            with open(ename, 'r') as fp:
81*9c5db199SXin Li                file_text = fp.read()
82*9c5db199SXin Li            raise error.TestError(
83*9c5db199SXin Li                    'Subprocess raised an exception that could not be '
84*9c5db199SXin Li                    'identified. The root cause exception is in the text '
85*9c5db199SXin Li                    'that follows: ' + file_text)
86*9c5db199SXin Li        finally:
87*9c5db199SXin Li            # Rename the error-pid file so that they do not affect later child
88*9c5db199SXin Li            # processes that use recycled pids.
89*9c5db199SXin Li            i = 0
90*9c5db199SXin Li            while True:
91*9c5db199SXin Li                pename = ename + ('-%d' % i)
92*9c5db199SXin Li                i += 1
93*9c5db199SXin Li                if not os.path.exists(pename):
94*9c5db199SXin Li                    break
95*9c5db199SXin Li            os.rename(ename, pename)
96*9c5db199SXin Li        raise e
97*9c5db199SXin Li
98*9c5db199SXin Li
99*9c5db199SXin Lidef fork_waitfor(tmp, pid):
100*9c5db199SXin Li    (pid, status) = os.waitpid(pid, 0)
101*9c5db199SXin Li
102*9c5db199SXin Li    _check_for_subprocess_exception(tmp, pid)
103*9c5db199SXin Li
104*9c5db199SXin Li    if status:
105*9c5db199SXin Li        raise error.TestError("Test subprocess failed rc=%d" % (status))
106*9c5db199SXin Li
107*9c5db199SXin Lidef fork_waitfor_timed(tmp, pid, timeout):
108*9c5db199SXin Li    """
109*9c5db199SXin Li    Waits for pid until it terminates or timeout expires.
110*9c5db199SXin Li    If timeout expires, test subprocess is killed.
111*9c5db199SXin Li    """
112*9c5db199SXin Li    timer_expired = True
113*9c5db199SXin Li    poll_time = 2
114*9c5db199SXin Li    time_passed = 0
115*9c5db199SXin Li    while time_passed < timeout:
116*9c5db199SXin Li        time.sleep(poll_time)
117*9c5db199SXin Li        (child_pid, status) = os.waitpid(pid, os.WNOHANG)
118*9c5db199SXin Li        if (child_pid, status) == (0, 0):
119*9c5db199SXin Li            time_passed = time_passed + poll_time
120*9c5db199SXin Li        else:
121*9c5db199SXin Li            timer_expired = False
122*9c5db199SXin Li            break
123*9c5db199SXin Li
124*9c5db199SXin Li    if timer_expired:
125*9c5db199SXin Li        logging.info('Timer expired (%d sec.), nuking pid %d', timeout, pid)
126*9c5db199SXin Li        utils.nuke_pid(pid)
127*9c5db199SXin Li        (child_pid, status) = os.waitpid(pid, 0)
128*9c5db199SXin Li        raise error.TestError("Test timeout expired, rc=%d" % (status))
129*9c5db199SXin Li    else:
130*9c5db199SXin Li        _check_for_subprocess_exception(tmp, pid)
131*9c5db199SXin Li
132*9c5db199SXin Li    if status:
133*9c5db199SXin Li        raise error.TestError("Test subprocess failed rc=%d" % (status))
134*9c5db199SXin Li
135*9c5db199SXin Lidef fork_nuke_subprocess(tmp, pid):
136*9c5db199SXin Li    utils.nuke_pid(pid)
137*9c5db199SXin Li    _check_for_subprocess_exception(tmp, pid)
138