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