Python 3.6.8
場景:特殊的網(wǎng)絡(luò)場景楣嘁,有多臺服務(wù)器的數(shù)據(jù)需要收集和整理鲸拥,其中有多個環(huán)節(jié)需進行SSH和SCP;網(wǎng)絡(luò)和服務(wù)器都是不穩(wěn)定的掉分,這時設(shè)置超時必不可少俭缓,比如,ssh認(rèn)證如果出現(xiàn)問題酥郭,shell命令會要求輸入密碼华坦,那條線就徹底掛起了,池子就變小了不从;而且這種掛起的日志容易被掩埋惜姐,導(dǎo)致問題無法及時處理;希望代碼對你有幫助
過程說明
最初使用了subprocess的getstatusoutput直接獲得執(zhí)行狀態(tài)和結(jié)果椿息,功能是達(dá)到了歹袁,但后續(xù)出現(xiàn)了兩種問題,導(dǎo)致任務(wù)堵塞了寝优。一個是ssh執(zhí)行命令条舔,一個是shell腳本執(zhí)行時間過長,其實是有異常"假死"了乏矾。這時任務(wù)應(yīng)該跳過繼續(xù)進行或重試孟抗,所以需要進行超時判斷,設(shè)置好超時時間妻熊。
首先我查了文檔夸浅,發(fā)現(xiàn)getstatusoutput封裝了check_output,底層是Popen都是有timeout參數(shù)的扔役,但設(shè)置上后還是無效帆喇。查看了之前的郵件組后發(fā)現(xiàn)了原因,關(guān)鍵是如果命令創(chuàng)建了新的子線程亿胸,子線程是沒有超時的坯钦,python不會選擇kill group预皇;因為python無法確定子線程要做什么,不能簡單粗暴婉刀,交由使用者決定怎么處理吟温。而且不同平臺的支持支持也不同,python不想做這吃力不討好的事突颊。
那么就自力更生吧鲁豪,我的目標(biāo)就是只要是超時就結(jié)束并返回異常。最終有了以下的代碼:
代碼
import subprocess
import time
import logging
# 執(zhí)行shell命令的方法
# cmd shell命令, timeout 超時:秒
# return (執(zhí)行狀態(tài):int律秃,執(zhí)行結(jié)果:str爬橡,執(zhí)行錯誤:str)
def run_cmd(cmd, timeout=None):
try:
exec_status = -1
exec_err = ""
exec_result = ""
# deprecated,無法提供超時功能
# exec_status, exec_result = subprocess.getstatusoutput(cmd)
try:
# shell=True 因為需要使用通道和字符串子命令棒动,所以默認(rèn)打開
with subprocess.Popen(cmd, shell=True, universal_newlines=True, stderr=subprocess.STDOUT,stdout=subprocess.PIPE) as process:
# 主要超時控制
if timeout:
to_count = 0
while True:
# interval sec: 5 因為還有很多l(xiāng)ong time command糙申,超時間隔不需要太細(xì)
time.sleep(5)
to_count += 5
if to_count > timeout:
# 回歸到subprocess的超時處理
process.kill()
raise subprocess.TimeoutExpired(process.args, timeout, output=None,
stderr=None)
if process.poll() is None:
continue
else:
break
try:
# 完成后獲取結(jié)果
stdout, stderr = process.communicate(input)
# 此處是subprocess的異常處理,直接使用
except subprocess.TimeoutExpired:
process.kill()
stdout, stderr = process.communicate()
raise subprocess.TimeoutExpired(process.args, timeout, output=stdout,
stderr=stderr)
except:
process.kill()
process.wait()
raise
retcode = process.poll()
if retcode:
raise subprocess.CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
data = subprocess.CompletedProcess(process.args, retcode, stdout, stderr).stdout
exec_status = 0
except subprocess.CalledProcessError as ex:
data = ex.output
exec_status = ex.returncode
if data[-1:] == '\n':
exec_result = data[:-1]
if exec_status != 0:
exec_err = exec_result
exec_result = None
return exec_status, exec_result, exec_err
# 自身封裝的異常處理船惨,為了記錄和返回統(tǒng)一格式
except subprocess.TimeoutExpired:
logging.error("cmd:{} is timeout".format(cmd))
return -9, None, "timeout"
except BaseException as ose:
logging.error("cmd:{} error:{}".format(cmd, ose.__str__()))
return -1, None, ose.__str__()
測試代碼
run_cmd("sleep 4m", 10)
run_cmd("python", 10)
run_cmd("python")