import time import os, sys import signal import subprocess import threading import queue import select from functools import wraps sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import core.logger as logger log = logger.get_logger() def timeit(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() execution_time = end_time - start_time cmd = args[0] log.trace(f"Spent [{execution_time:.4f}s] Run: [ {cmd} ]") return result return wrapper @timeit def shell(cmd, *kwargs, timeout:float=10, checkret:bool=True, async_handle=None, handle_param=None): executer = ShellExecute(cmd, timeout, async_handle, handle_param) ret, output, error = executer.execute() if checkret and ret!= 0: log.error(f"Execute [\n{cmd}\n] failed Ret:{ret}") log.debug(f"Stdout:{output}") log.error(f"Stderr:{error}") raise Exception("error execute shell command") return ret, output, error class ShellExecute(threading.Thread): def __init__(self, cmd_line, timeout:int, async_handle, handle_param): super().__init__(target=self.monitor) self.cmd_line = cmd_line self.timeout = timeout self.async_handle = async_handle self.handle_param = handle_param def monitor(self): if self.timeout is None: log.debug("Block to execute [{}] ".format(self.cmd_line)) return cnt = self.timeout / 0.1 while self.process.poll() is None and cnt > 0: time.sleep(0.1) cnt -= 1 if self.process.poll() is not None: # 进程已经结束 return os.kill(self.process.pid, signal.SIGSEGV) log.error("execute [{}] timeout, Force kill it(pid:{})...".format(self.cmd_line, self.process.pid)) raise TimeoutError("Execute cmd timeout") def execute(self): if self.async_handle is not None: return self.async_execute() else: return self.sync_execute() def sync_execute(self): process = subprocess.Popen(self.cmd_line, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.process = process self.start() #启动监控线程 stdout, stderr = process.communicate() output = stdout.decode('utf-8') error = stderr.decode('utf-8') return_code = process.returncode return return_code, output, error def async_execute(self): output_queue = queue.Queue() # read_fd, write_fd = os.pipe() thread = threading.Thread(target=self.async_handle, args=(output_queue,self.handle_param), daemon=True) thread.start() process = subprocess.Popen(self.cmd_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) self.process = process self.start() #启动监控线程 try: epoll = select.epoll() epoll.register(process.stdout.fileno(), select.EPOLLIN) while process.poll() is None: events = epoll.poll() for fileno, _ in events: if fileno != process.stdout.fileno(): continue while True: std_out = process.stdout.readline() if not std_out: break output_queue.put(std_out) # 将输出放入队列 except KeyboardInterrupt: log.warning("Interrupted Process") os.kill(self.process.pid, signal.SIGSEGV) log.trace("Process finished.") output_queue.put(None) # 结束队列 thread.join() # 等待线程结束 stdout, stderr = process.communicate() output = stdout#.decode('utf-8') error = stderr#.decode('utf-8') return_code = process.returncode return return_code, output, error