การใช้โมดูล 'กระบวนการย่อย' กับการหมดเวลา


325

นี่คือรหัสไพ ธ อนเพื่อเรียกใช้คำสั่งโดยพลการส่งคืนstdoutข้อมูลหรือเพิ่มข้อยกเว้นรหัสการออกที่ไม่เป็นศูนย์:

proc = subprocess.Popen(
    cmd,
    stderr=subprocess.STDOUT,  # Merge stdout and stderr
    stdout=subprocess.PIPE,
    shell=True)

communicate ถูกใช้เพื่อรอให้กระบวนการออกจาก:

stdoutdata, stderrdata = proc.communicate()

subprocessโมดูลไม่สนับสนุนการหมดเวลา - ความสามารถในการฆ่ากระบวนการทำงานมานานกว่าจำนวน X วินาที - จึงcommunicateอาจใช้เวลาตลอดไปวิ่ง

เป็นอะไรที่ง่ายวิธีที่จะใช้หมดเวลาในโปรแกรมหลามหมายถึงการทำงานบน Windows และ Linux?


2
รายการตัวติดตามปัญหา Python ที่เกี่ยวข้อง: bugs.python.org/issue5673
Sridhar Ratnakumar

10
ใช้pypi.python.org/pypi/subprocess32สำหรับ Python2.x มันเป็น backport ของ Python 3.x มันมีอาร์กิวเมนต์การหมดเวลาสำหรับการโทร () และรอ ()
guettli

1
pypi.python.org/pypi/subprocess32 ไม่สามารถใช้งานบน Windows :(
adrianX

คำตอบ:


170

ใน Python 3.3+:

from subprocess import STDOUT, check_output

output = check_output(cmd, stderr=STDOUT, timeout=seconds)

output เป็นสตริงไบต์ที่มีคำสั่ง stdout ที่ผสานข้อมูล stderr

check_outputเพิ่มCalledProcessErrorสถานะการออกที่ไม่ใช่ศูนย์ตามที่ระบุในข้อความของคำถามซึ่งแตกต่างจากproc.communicate()วิธีการ

ฉันลบshell=Trueเพราะมันมักจะใช้โดยไม่จำเป็น คุณสามารถเพิ่มกลับคืนได้หากcmdต้องการ หากคุณเพิ่มshell=Trueเช่นถ้ากระบวนการลูกวางไข่ของตัวเอง; check_output()สามารถกลับมาช้ากว่าเวลาที่ระบุได้โปรดดูความล้มเหลวการหมดเวลาของกระบวนการ

คุณสมบัติการหมดเวลาใช้งานบน Python 2.x ผ่านทางsubprocess32backport ของโมดูลย่อย 3.2+


17
แน่นอนและการสนับสนุนการหมดเวลาของกระบวนการย่อยมีอยู่ใน backport subprocess32 ที่ฉันดูแลเพื่อใช้กับ Python 2 pypi.python.org/pypi/subprocess32
gps

8
@gps Sridhar ขอโซลูชันข้ามแพลตฟอร์มในขณะที่ backport ของคุณรองรับ POSIX เท่านั้น: เมื่อฉันลองใช้ MSVC บ่น (คาดว่า) เกี่ยวกับ unistd.h ที่หายไป :)
Shmil The Cat

หากคุณไม่ต้องการเอาต์พุตคุณสามารถใช้ subprocess.call
Kyle Gibson

ตั้งแต่ Python3.5 ให้ใช้ subprocess.run () กับ capture_output = True และใช้พารามิเตอร์การเข้ารหัสเพื่อรับเอาต์พุต usefoul
MKesper

1
@MKesper: 1- check_output()เป็นวิธีที่ต้องการในการรับเอาท์พุท (ส่งคืนเอาต์พุตโดยตรงไม่เพิกเฉยต่อข้อผิดพลาดมันมีให้ใช้ตลอดไป) 2- run()มีความยืดหยุ่นมากขึ้น แต่run()ไม่สนใจข้อผิดพลาดโดยค่าเริ่มต้นและต้องการขั้นตอนเพิ่มเติมเพื่อให้ได้ผลลัพธ์ 3- check_output()มีการใช้งานในแง่ของrun()ดังนั้นจึงยอมรับอาร์กิวเมนต์ส่วนใหญ่เดียวกัน 4- nit: capture_outputมีให้ตั้งแต่ 3.7 ไม่ใช่ 3.5
jfs

205

ฉันไม่รู้เกี่ยวกับรายละเอียดในระดับต่ำมากนัก แต่เนื่องจากใน python 2.6 API มีความสามารถในการรอเธรดและยุติกระบวนการสิ่งที่เกี่ยวกับการรันกระบวนการในเธรดแยกต่างหาก

import subprocess, threading

class Command(object):
    def __init__(self, cmd):
        self.cmd = cmd
        self.process = None

    def run(self, timeout):
        def target():
            print 'Thread started'
            self.process = subprocess.Popen(self.cmd, shell=True)
            self.process.communicate()
            print 'Thread finished'

        thread = threading.Thread(target=target)
        thread.start()

        thread.join(timeout)
        if thread.is_alive():
            print 'Terminating process'
            self.process.terminate()
            thread.join()
        print self.process.returncode

command = Command("echo 'Process started'; sleep 2; echo 'Process finished'")
command.run(timeout=3)
command.run(timeout=1)

ผลลัพธ์ของตัวอย่างนี้ในเครื่องของฉันคือ:

Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15

ซึ่งสามารถเห็นได้ว่าในการดำเนินการครั้งแรกกระบวนการเสร็จสิ้นอย่างถูกต้อง (รหัสส่งคืน 0) ในขณะที่กระบวนการที่สองสิ้นสุดลง (รหัสส่งคืน -15)

ฉันยังไม่ได้ทดสอบใน windows แต่นอกเหนือจากการอัปเดตคำสั่งตัวอย่างฉันคิดว่ามันควรจะทำงานได้เนื่องจากฉันไม่พบเอกสารอะไรที่ระบุว่า thread.join หรือ process.terminate ไม่ได้รับการสนับสนุน


16
+1 สำหรับการเป็นแพลตฟอร์มที่เป็นอิสระ ฉันใช้งานทั้ง linux และ windows 7 (cygwin และ plain windows python) - ทำงานได้ตามที่คาดไว้ในทั้งสามกรณี
phooji

7
ฉันได้แก้ไขโค้ดของคุณเล็กน้อยเพื่อให้สามารถส่ง kwargs ของ Popen ดั้งเดิมและวางไว้ในส่วนสำคัญ ตอนนี้พร้อมที่จะใช้เอนกประสงค์ gist.github.com/1306188
kirpit

2
สำหรับใครที่มีปัญหา @redice มีคำถามนี้อาจช่วย กล่าวโดยย่อหากคุณใช้ shell = True เชลล์จะกลายเป็นกระบวนการลูกที่ถูกฆ่าและคำสั่ง (ลูกของกระบวนการลูก) จะเปิดใช้งาน
Anson

6
คำตอบนี้ไม่ได้มีฟังก์ชั่นการทำงานเหมือนกันกับต้นฉบับเพราะมันไม่ส่งคืน stdout
stephenbez

2
thread.is_alive สามารถนำไปสู่สภาพการแข่งขัน ดูostricher.com/2015/01/python-subprocess-with-timeout
ChaimKut

132

คำตอบของ jcollado สามารถทำให้ง่ายขึ้นโดยใช้threading.Timer class:

import shlex
from subprocess import Popen, PIPE
from threading import Timer

def run(cmd, timeout_sec):
    proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
    timer = Timer(timeout_sec, proc.kill)
    try:
        timer.start()
        stdout, stderr = proc.communicate()
    finally:
        timer.cancel()

# Examples: both take 1 second
run("sleep 1", 5)  # process ends normally at 1 second
run("sleep 5", 1)  # timeout happens at 1 second

11
+1 สำหรับโซลูชันแบบพกพาที่เรียบง่าย คุณไม่ต้องการlambda:t = Timer(timeout, proc.kill)
jfs

3
+1 สิ่งนี้ควรเป็นคำตอบที่ยอมรับได้เนื่องจากไม่จำเป็นต้องมีวิธีในการเปิดตัวกระบวนการที่จะเปลี่ยนแปลง
Dave Branton

1
ทำไมถึงต้องใช้แลมบ์ดา? ไม่สามารถใช้เมธอด p.kill ได้โดยไม่ต้องใช้แลมบ์ดาที่นั่นใช่ไหม
Danny Staple

//, คุณยินดีที่จะรวมตัวอย่างการใช้สิ่งนี้หรือไม่?
Nathan Basanese

1
@tuk timer.isAlive()ก่อนtimer.cancel()หมายความว่ามันจะจบลงตามปกติ
ชาร์ลส์

83

ถ้าคุณใช้ระบบยูนิกซ์

import signal
  ...
class Alarm(Exception):
    pass

def alarm_handler(signum, frame):
    raise Alarm

signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(5*60)  # 5 minutes
try:
    stdoutdata, stderrdata = proc.communicate()
    signal.alarm(0)  # reset the alarm
except Alarm:
    print "Oops, taking too long!"
    # whatever else

3
ฉันสนใจโซลูชันข้ามแพลตฟอร์มที่ทำงานอย่างน้อยใน win / linux / mac
Sridhar Ratnakumar

1
ฉันชอบวิธีที่ใช้ระบบยูนิกซ์ จะเป็นการดีที่จะรวมสิ่งนี้กับวิธีการเฉพาะ windows (ใช้ CreateProcess และงาน) .. แต่ตอนนี้วิธีการแก้ปัญหาด้านล่างเป็นเรื่องง่ายง่ายและใช้งานได้จนถึงตอนนี้
Sridhar Ratnakumar

3
ฉันได้เพิ่มโซลูชันแบบพกพาดูคำตอบของฉัน
flybywire

4
วิธีการแก้ปัญหานี้จะทำงานonly_if signal.signal (signal.SIGALARM, alarm_handler) ถูกเรียกจากเธรดหลัก ดูเอกสารประกอบสำหรับสัญญาณ
volatilevoid

น่าเสียดายที่เมื่อใช้งาน (บน linux) ในบริบทของโมดูล Apache (เช่น mod_python, mod_perl หรือ mod_php) ฉันพบว่าการใช้สัญญาณและการเตือนนั้นไม่ได้รับอนุญาต (น่าจะเป็นเพราะยุ่งกับตรรกะ IPC ของ Apache) ดังนั้นเพื่อให้บรรลุเป้าหมายของการกำหนดเวลาออกคำสั่งฉันถูกบังคับให้เขียน "ลูปพาเรนต์" ซึ่งเปิดใช้กระบวนการเด็กและจากนั้นนั่งในวง "นอน" y ดูนาฬิกา (และอาจตรวจสอบผลลัพธ์จากเด็ก)
ปีเตอร์

44

นี่คือโซลูชันของ Alex Martelli ในฐานะโมดูลที่มีกระบวนการฆ่าที่เหมาะสม วิธีการอื่นไม่ทำงานเพราะไม่ได้ใช้ proc.communicate () ดังนั้นหากคุณมีกระบวนการที่สร้างเอาต์พุตจำนวนมากมันจะเติมบัฟเฟอร์เอาต์พุตของมันแล้วบล็อกจนกว่าคุณจะอ่านบางอย่างจากมัน

from os import kill
from signal import alarm, signal, SIGALRM, SIGKILL
from subprocess import PIPE, Popen

def run(args, cwd = None, shell = False, kill_tree = True, timeout = -1, env = None):
    '''
    Run a command with a timeout after which it will be forcibly
    killed.
    '''
    class Alarm(Exception):
        pass
    def alarm_handler(signum, frame):
        raise Alarm
    p = Popen(args, shell = shell, cwd = cwd, stdout = PIPE, stderr = PIPE, env = env)
    if timeout != -1:
        signal(SIGALRM, alarm_handler)
        alarm(timeout)
    try:
        stdout, stderr = p.communicate()
        if timeout != -1:
            alarm(0)
    except Alarm:
        pids = [p.pid]
        if kill_tree:
            pids.extend(get_process_children(p.pid))
        for pid in pids:
            # process might have died before getting to this line
            # so wrap to avoid OSError: no such process
            try: 
                kill(pid, SIGKILL)
            except OSError:
                pass
        return -9, '', ''
    return p.returncode, stdout, stderr

def get_process_children(pid):
    p = Popen('ps --no-headers -o pid --ppid %d' % pid, shell = True,
              stdout = PIPE, stderr = PIPE)
    stdout, stderr = p.communicate()
    return [int(p) for p in stdout.split()]

if __name__ == '__main__':
    print run('find /', shell = True, timeout = 3)
    print run('find', shell = True)

3
สิ่งนี้จะไม่ทำงานบน windows รวมถึงลำดับการทำงานที่กลับด้าน
Hamish Grubijan

3
บางครั้งผลลัพธ์นี้เป็นข้อยกเว้นเมื่อผู้จัดการรายอื่นลงทะเบียนตัวเองใน SIGALARM และฆ่ากระบวนการก่อนที่สิ่งนี้จะ "ฆ่า" เพิ่มการทำงานรอบ ๆ BTW สูตรที่ยอดเยี่ยม! ฉันใช้วิธีนี้เพื่อเปิดใช้งานกระบวนการ 50,000 buggy จนถึงขณะนี้โดยไม่ต้องแช่แข็งหรือหยุดการทำงานของ wrapper
Yaroslav Bulatov

สิ่งนี้จะถูกปรับเปลี่ยนให้ทำงานในแอปพลิเคชั่นที่เป็นเธรดได้อย่างไร ฉันกำลังพยายามที่จะใช้จากภายในเธรดและได้รับValueError: signal only works in main thread
Wim

@ Yaroslav Bulatov ขอบคุณสำหรับข้อมูล วิธีแก้ปัญหาที่คุณเพิ่มเข้ามาเพื่อจัดการกับปัญหาที่กล่าวถึงคืออะไร
jpswain

1
เพิ่งเพิ่มบล็อค "ลอง; จับ" มันอยู่ในรหัส BTW ในระยะยาวสิ่งนี้กลับกลายเป็นว่าฉันมีปัญหาเพราะคุณสามารถตั้งค่า SIGALARM handler ได้เพียงหนึ่งตัวเท่านั้นและกระบวนการอื่น ๆ สามารถรีเซ็ตได้ วิธีแก้ไขปัญหานี้มีให้ที่นี่ - stackoverflow.com/questions/6553423/…
Yaroslav Bulatov

18

ฉันแก้ไขคำตอบsussudioแล้ว ตอนนี้ทำงานผลตอบแทน ( returncode, stdout, stderr, timeout) - stdoutและstderrถอดรหัสให้เป็น UTF-8 สตริง

def kill_proc(proc, timeout):
  timeout["value"] = True
  proc.kill()

def run(cmd, timeout_sec):
  proc = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  timeout = {"value": False}
  timer = Timer(timeout_sec, kill_proc, [proc, timeout])
  timer.start()
  stdout, stderr = proc.communicate()
  timer.cancel()
  return proc.returncode, stdout.decode("utf-8"), stderr.decode("utf-8"), timeout["value"]

18

แปลกใจไม่มีใครพูดถึงการใช้ timeout

timeout 5 ping -c 3 somehost

สิ่งนี้จะไม่เหมาะกับการใช้งานทุกกรณีอย่างเห็นได้ชัด แต่ถ้าคุณจัดการกับสคริปต์ง่าย ๆ มันยากที่จะเอาชนะ

นอกจากนี้ยังมีให้ใช้เป็น gtimeout ใน coreutils ผ่านhomebrewสำหรับผู้ใช้ mac


1
proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...)คุณหมายถึง: มีtimeoutคำสั่งบน Windows เมื่อ OP ถามหรือไม่
jfs

ใน windows สามารถใช้แอปพลิเคชั่นเช่นgit bashซึ่งอนุญาตให้มียูทิลิตี bash ใน Windows
Kaushik Acharya

@KaushikAcharya แม้ว่าคุณจะใช้ git bash เมื่อไพ ธ อนเรียก subprocess มันก็จะทำงานบน Windows ดังนั้นบายพาสนี้จะไม่ทำงาน
Naman Chikara

16

timeoutตอนนี้ได้รับการสนับสนุนโดยcall()และcommunicate()ในโมดูลย่อย (ตั้งแต่ Python3.3):

import subprocess

subprocess.call("command", timeout=20, shell=True)

นี้จะเรียกคำสั่งและยกข้อยกเว้น

subprocess.TimeoutExpired

หากคำสั่งไม่เสร็จสิ้นหลังจาก 20 วินาที

จากนั้นคุณสามารถจัดการข้อยกเว้นเพื่อดำเนินการต่อรหัสของคุณเช่น:

try:
    subprocess.call("command", timeout=20, shell=True)
except subprocess.TimeoutExpired:
    # insert code here

หวังว่านี่จะช่วยได้


มีคำตอบที่มีอยู่ที่กล่าวถึงtimeoutพารามิเตอร์ แม้ว่าการกล่าวถึงอีกครั้งจะไม่เจ็บ
jfs

//, ฉันคิดว่า OP กำลังมองหาวิธีแก้ปัญหาสำหรับ Python รุ่นเก่า
Nathan Basanese

11

อีกทางเลือกหนึ่งคือการเขียนไปยังไฟล์ชั่วคราวเพื่อป้องกันการบล็อก stdout แทนที่จะต้องสำรวจด้วยการสื่อสาร () สิ่งนี้ใช้ได้กับฉันในกรณีที่คำตอบอื่นไม่ได้ ตัวอย่างเช่นบน windows

    outFile =  tempfile.SpooledTemporaryFile() 
    errFile =   tempfile.SpooledTemporaryFile() 
    proc = subprocess.Popen(args, stderr=errFile, stdout=outFile, universal_newlines=False)
    wait_remaining_sec = timeout

    while proc.poll() is None and wait_remaining_sec > 0:
        time.sleep(1)
        wait_remaining_sec -= 1

    if wait_remaining_sec <= 0:
        killProc(proc.pid)
        raise ProcessIncompleteError(proc, timeout)

    # read temp streams from start
    outFile.seek(0);
    errFile.seek(0);
    out = outFile.read()
    err = errFile.read()
    outFile.close()
    errFile.close()

ดูเหมือนว่าไม่สมบูรณ์ - tempfile คืออะไร
spiderplant0

รวม "import tempfile", "เวลานำเข้า" และ "shell = True" ภายในการโทร "Popen" (ระวังด้วย "shell = True")!
Eduardo Lucio

11

ฉันไม่รู้ว่าทำไมถึงไม่พูดถึง แต่ตั้งแต่ Python 3.5 มีsubprocess.runคำสั่งสากลใหม่(นั่นหมายถึงการแทนที่check_call, check_output... ) และมีtimeoutพารามิเตอร์เช่นกัน

subprocess.run (args, *, stdin = None, input = None, stdout = None, stderr = ไม่มี, shell = False, cwd = None, หมดเวลา = ไม่มี, ตรวจสอบ = False, เข้ารหัส = ไม่มี, ข้อผิดพลาด = ไม่มี)

Run the command described by args. Wait for command to complete, then return a CompletedProcess instance.

มันทำให้เกิดsubprocess.TimeoutExpiredข้อยกเว้นเมื่อหมดเวลาหมดเวลา


6

นี่คือทางออกของฉันฉันใช้เธรดและกิจกรรม:

import subprocess
from threading import Thread, Event

def kill_on_timeout(done, timeout, proc):
    if not done.wait(timeout):
        proc.kill()

def exec_command(command, timeout):

    done = Event()
    proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    watcher = Thread(target=kill_on_timeout, args=(done, timeout, proc))
    watcher.daemon = True
    watcher.start()

    data, stderr = proc.communicate()
    done.set()

    return data, stderr, proc.returncode

ในการดำเนินการ:

In [2]: exec_command(['sleep', '10'], 5)
Out[2]: ('', '', -9)

In [3]: exec_command(['sleep', '10'], 11)
Out[3]: ('', '', 0)

5

วิธีที่ฉันใช้คือนำหน้าคำสั่งเชลล์ด้วย timelimit timelimitหาก comand ใช้เวลานานเกินไป timelimit จะหยุดมันและ Popen จะมีรหัสส่งคืนที่กำหนดโดย timelimit หากเป็น> 128 หมายถึงว่า timelimit ฆ่ากระบวนการ

ดูเพิ่มเติมที่python subprocess พร้อมหมดเวลาและเอาต์พุตขนาดใหญ่ (> 64K)


ฉันใช้เครื่องมือที่คล้ายกันที่เรียกว่าtimeout- packages.ubuntu.com/search?keywords=timeout - แต่ไม่สามารถใช้งานได้บน Windows ใช่ไหม
Sridhar Ratnakumar

5

ฉันเพิ่มโซลูชันด้วยเธรดจากjcolladoไปยังโมดูล Python easyprocessของฉัน easyprocess

ติดตั้ง:

pip install easyprocess

ตัวอย่าง:

from easyprocess import Proc

# shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout

โมดูล easyprocess ( code.activestate.com/pypm/easyprocess ) ทำงานให้ฉันแม้จะใช้จากการประมวลผลหลายตัว
iChux

5

ถ้าคุณใช้ python 2 ลองทำดู

import subprocess32

try:
    output = subprocess32.check_output(command, shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
    print e

1
อาจไม่ทำงานบน Windows ตามที่ถามในคำถามเริ่มต้น
Jean-Francois T.

5

การเตรียมคำสั่ง Linux timeoutไม่ใช่วิธีแก้ปัญหาที่ไม่ดีและมันใช้ได้ผลสำหรับฉัน

cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()

ฉันจะรับสายที่พิมพ์ออกมาระหว่างการดำเนินการกระบวนการย่อยได้อย่างไร - ข้อความแจ้งเตือนจะถูกส่งคืนโดยกระบวนการย่อย
Ammad

3

ฉันใช้สิ่งที่ฉันสามารถรวบรวมได้จากสิ่งเหล่านี้ สิ่งนี้ใช้ได้ใน Windows และเนื่องจากนี่เป็นวิกิชุมชนฉันจึงคิดว่าฉันจะแชร์รหัสของฉันด้วย:

class Command(threading.Thread):
    def __init__(self, cmd, outFile, errFile, timeout):
        threading.Thread.__init__(self)
        self.cmd = cmd
        self.process = None
        self.outFile = outFile
        self.errFile = errFile
        self.timed_out = False
        self.timeout = timeout

    def run(self):
        self.process = subprocess.Popen(self.cmd, stdout = self.outFile, \
            stderr = self.errFile)

        while (self.process.poll() is None and self.timeout > 0):
            time.sleep(1)
            self.timeout -= 1

        if not self.timeout > 0:
            self.process.terminate()
            self.timed_out = True
        else:
            self.timed_out = False

จากคลาสหรือไฟล์อื่น:

        outFile =  tempfile.SpooledTemporaryFile()
        errFile =   tempfile.SpooledTemporaryFile()

        executor = command.Command(c, outFile, errFile, timeout)
        executor.daemon = True
        executor.start()

        executor.join()
        if executor.timed_out:
            out = 'timed out'
        else:
            outFile.seek(0)
            errFile.seek(0)
            out = outFile.read()
            err = errFile.read()

        outFile.close()
        errFile.close()

จริงๆแล้วนี่อาจไม่ได้ผล terminate()เครื่องหมายฟังก์ชั่นด้ายเป็นสิ้นสุด แต่ไม่จริงยุติด้าย! ฉันสามารถตรวจสอบได้ใน * nix แต่ฉันไม่มีคอมพิวเตอร์ Windows ที่จะทำการทดสอบ
dotancohen

2

เมื่อคุณเข้าใจเครื่องจักรที่ทำงานแบบเต็มกระบวนการใน * unix คุณจะพบโซลูชันที่ง่ายกว่า:

ลองพิจารณาตัวอย่างง่ายๆนี้ว่าจะทำให้การสื่อสารที่หมดเวลาได้อย่างไร () ปรุงโดยใช้ select.select () นอกจากนี้ยังสามารถเขียนด้วย epoll / Poll / kqueue แต่ตัวแปร select.select () อาจเป็นตัวอย่างที่ดีสำหรับคุณ และข้อ จำกัด ที่สำคัญของ select.select () (ความเร็วและ 1024 สูงสุด fds) จะไม่สามารถใช้ได้กับงานของคุณ

สิ่งนี้ทำงานภายใต้ * nix, ไม่ได้สร้างเธรด, ไม่ใช้สัญญาณ, สามารถ lauched จากเธรดใด ๆ (ไม่เพียงหลัก), และรวดเร็วพอที่จะอ่านข้อมูล 250mb / s จาก stdout บนเครื่องของฉัน (i5 2.3ghz)

มีปัญหาในการเข้าร่วม stdout / stderr เมื่อสิ้นสุดการสื่อสาร หากคุณมีเอาต์พุตของโปรแกรมจำนวนมากสิ่งนี้อาจนำไปสู่การใช้หน่วยความจำขนาดใหญ่ แต่คุณสามารถโทรหาการสื่อสาร () หลายครั้งด้วยเวลาที่น้อยลง

class Popen(subprocess.Popen):
    def communicate(self, input=None, timeout=None):
        if timeout is None:
            return subprocess.Popen.communicate(self, input)

        if self.stdin:
            # Flush stdio buffer, this might block if user
            # has been writing to .stdin in an uncontrolled
            # fashion.
            self.stdin.flush()
            if not input:
                self.stdin.close()

        read_set, write_set = [], []
        stdout = stderr = None

        if self.stdin and input:
            write_set.append(self.stdin)
        if self.stdout:
            read_set.append(self.stdout)
            stdout = []
        if self.stderr:
            read_set.append(self.stderr)
            stderr = []

        input_offset = 0
        deadline = time.time() + timeout

        while read_set or write_set:
            try:
                rlist, wlist, xlist = select.select(read_set, write_set, [], max(0, deadline - time.time()))
            except select.error as ex:
                if ex.args[0] == errno.EINTR:
                    continue
                raise

            if not (rlist or wlist):
                # Just break if timeout
                # Since we do not close stdout/stderr/stdin, we can call
                # communicate() several times reading data by smaller pieces.
                break

            if self.stdin in wlist:
                chunk = input[input_offset:input_offset + subprocess._PIPE_BUF]
                try:
                    bytes_written = os.write(self.stdin.fileno(), chunk)
                except OSError as ex:
                    if ex.errno == errno.EPIPE:
                        self.stdin.close()
                        write_set.remove(self.stdin)
                    else:
                        raise
                else:
                    input_offset += bytes_written
                    if input_offset >= len(input):
                        self.stdin.close()
                        write_set.remove(self.stdin)

            # Read stdout / stderr by 1024 bytes
            for fn, tgt in (
                (self.stdout, stdout),
                (self.stderr, stderr),
            ):
                if fn in rlist:
                    data = os.read(fn.fileno(), 1024)
                    if data == '':
                        fn.close()
                        read_set.remove(fn)
                    tgt.append(data)

        if stdout is not None:
            stdout = ''.join(stdout)
        if stderr is not None:
            stderr = ''.join(stderr)

        return (stdout, stderr)

2
สิ่งนี้แก้ปัญหา Unix ครึ่งหนึ่งของปัญหาเท่านั้น
Spaceghost

2

คุณสามารถทำได้โดยใช้ select

import subprocess
from datetime import datetime
from select import select

def call_with_timeout(cmd, timeout):
    started = datetime.now()
    sp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    while True:
        p = select([sp.stdout], [], [], timeout)
        if p[0]:
            p[0][0].read()
        ret = sp.poll()
        if ret is not None:
            return ret
        if (datetime.now()-started).total_seconds() > timeout:
            sp.kill()
            return None

1

ฉันใช้killableprocessสำเร็จแล้วใน Windows, Linux และ Mac หากคุณกำลังใช้ Cygwin Python คุณจะต้องkillableprocess เวอร์ชันของ OSAFเพราะมิฉะนั้นกระบวนการ Windows ดั้งเดิมจะไม่ถูกทำลาย


ดูเหมือนว่า killableprocess จะไม่เพิ่มการหมดเวลาลงในการโทร Popen.communicate ()
Wim Coenen

1

แม้ว่าฉันจะไม่ได้ดูมันอย่างกว้างขวางนักตกแต่งที่ฉันพบที่ ActiveState นี้ดูเหมือนจะมีประโยชน์มากสำหรับสิ่งนี้ พร้อมกับsubprocess.Popen(..., close_fds=True)อย่างน้อยฉันพร้อมสำหรับเปลือกสคริปต์ในหลาม


มัณฑนากรนี้ใช้สัญญาณ. ปลุกซึ่งไม่สามารถใช้ได้บน Windows
dbn

1

โซลูชันนี้ฆ่าแผนผังกระบวนการในกรณีที่เชลล์ = True ส่งพารามิเตอร์ไปยังกระบวนการ (หรือไม่) มีการหมดเวลาและรับ stdout, stderr และเอาต์พุตผลลัพธ์ของการเรียกกลับ (ใช้ psutil สำหรับ kill_proc_tree) สิ่งนี้มีพื้นฐานอยู่บนโซลูชั่นหลายรายการที่โพสต์ใน SO รวมถึง jcollado โพสต์ในการตอบสนองต่อความคิดเห็นโดย Anson และ jradice ในคำตอบของ jcollado ผ่านการทดสอบใน Windows Srvr 2012 และ Ubuntu 14.04 โปรดทราบว่าสำหรับ Ubuntu คุณต้องเปลี่ยนการโทร parent.children (... ) เป็น parent.get_children (... )

def kill_proc_tree(pid, including_parent=True):
  parent = psutil.Process(pid)
  children = parent.children(recursive=True)
  for child in children:
    child.kill()
  psutil.wait_procs(children, timeout=5)
  if including_parent:
    parent.kill()
    parent.wait(5)

def run_with_timeout(cmd, current_dir, cmd_parms, timeout):
  def target():
    process = subprocess.Popen(cmd, cwd=current_dir, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)

    # wait for the process to terminate
    if (cmd_parms == ""):
      out, err = process.communicate()
    else:
      out, err = process.communicate(cmd_parms)
    errcode = process.returncode

  thread = Thread(target=target)
  thread.start()

  thread.join(timeout)
  if thread.is_alive():
    me = os.getpid()
    kill_proc_tree(me, including_parent=False)
    thread.join()

1

มีความคิดที่จะซับคลาสคลาส Popen และขยายด้วยตัวตกแต่งวิธีการง่ายๆ เรียกมันว่า ExpirablePopen

from logging import error
from subprocess import Popen
from threading import Event
from threading import Thread


class ExpirablePopen(Popen):

    def __init__(self, *args, **kwargs):
        self.timeout = kwargs.pop('timeout', 0)
        self.timer = None
        self.done = Event()

        Popen.__init__(self, *args, **kwargs)

    def __tkill(self):
        timeout = self.timeout
        if not self.done.wait(timeout):
            error('Terminating process {} by timeout of {} secs.'.format(self.pid, timeout))
            self.kill()

    def expirable(func):
        def wrapper(self, *args, **kwargs):
            # zero timeout means call of parent method
            if self.timeout == 0:
                return func(self, *args, **kwargs)

            # if timer is None, need to start it
            if self.timer is None:
                self.timer = thr = Thread(target=self.__tkill)
                thr.daemon = True
                thr.start()

            result = func(self, *args, **kwargs)
            self.done.set()

            return result
        return wrapper

    wait = expirable(Popen.wait)
    communicate = expirable(Popen.communicate)


if __name__ == '__main__':
    from subprocess import PIPE

    print ExpirablePopen('ssh -T git@bitbucket.org', stdout=PIPE, timeout=1).communicate()

1

ฉันมีปัญหาที่ฉันต้องการที่จะยุติกระบวนการย่อยมัลติเธรดถ้ามันใช้เวลานานกว่าระยะเวลาที่กำหนด ฉันต้องการหมดเวลาในPopen()แต่ไม่ได้ผล จากนั้นฉันตระหนักว่าPopen().wait()เท่ากับcall()และดังนั้นฉันจึงมีความคิดที่จะตั้งค่าการหมดเวลาภายใน.wait(timeout=xxx)วิธีการซึ่งในที่สุดก็ใช้งานได้ ดังนั้นฉันแก้ไขมันด้วยวิธีนี้:

import os
import sys
import signal
import subprocess
from multiprocessing import Pool

cores_for_parallelization = 4
timeout_time = 15  # seconds

def main():
    jobs = [...YOUR_JOB_LIST...]
    with Pool(cores_for_parallelization) as p:
        p.map(run_parallel_jobs, jobs)

def run_parallel_jobs(args):
    # Define the arguments including the paths
    initial_terminal_command = 'C:\\Python34\\python.exe'  # Python executable
    function_to_start = 'C:\\temp\\xyz.py'  # The multithreading script
    final_list = [initial_terminal_command, function_to_start]
    final_list.extend(args)

    # Start the subprocess and determine the process PID
    subp = subprocess.Popen(final_list)  # starts the process
    pid = subp.pid

    # Wait until the return code returns from the function by considering the timeout. 
    # If not, terminate the process.
    try:
        returncode = subp.wait(timeout=timeout_time)  # should be zero if accomplished
    except subprocess.TimeoutExpired:
        # Distinguish between Linux and Windows and terminate the process if 
        # the timeout has been expired
        if sys.platform == 'linux2':
            os.kill(pid, signal.SIGTERM)
        elif sys.platform == 'win32':
            subp.terminate()

if __name__ == '__main__':
    main()

0

น่าเสียดายที่ฉันผูกพันตามนโยบายที่เข้มงวดมากในการเปิดเผยซอร์สโค้ดโดยนายจ้างของฉันดังนั้นฉันจึงไม่สามารถให้รหัสจริงได้ แต่สำหรับรสนิยมของฉันทางออกที่ดีที่สุดคือการสร้างคลาสย่อยที่แทนที่Popen.wait()โพลแทนที่จะรออย่างไม่มีกำหนดและPopen.__init__ยอมรับพารามิเตอร์ไทม์เอาต์ เมื่อคุณทำอย่างนั้นคนอื่น ๆPopenวิธีการ (ที่โทรwait) communicateจะทำงานตามที่คาดไว้รวมทั้ง


0

https://pypi.python.org/pypi/python-subprocess2จัดเตรียมส่วนขยายให้กับโมดูลย่อยซึ่งช่วยให้คุณสามารถรอเป็นระยะเวลาหนึ่งไม่เช่นนั้นจะสิ้นสุด

ดังนั้นเพื่อรอนานถึง 10 วินาทีเพื่อให้กระบวนการยุติมิฉะนั้นฆ่า:

pipe  = subprocess.Popen('...')

timeout =  10

results = pipe.waitOrTerminate(timeout)

สิ่งนี้เข้ากันได้กับทั้ง windows และ unix "results" เป็นพจนานุกรมมันมี "returnCode" ซึ่งเป็นการส่งคืนแอป (หรือไม่มีหากต้องถูกฆ่า) รวมถึง "actionTaken" ซึ่งจะเป็น "SUBPROCESS2_PROCESS_COMPLETED" หากกระบวนการเสร็จสมบูรณ์ตามปกติหรือมาส์กของ "SUBPROCESS2_PROCESS_TERMINATED" และ SUBPROCESS2_PROCESS_KILLED ขึ้นอยู่กับการดำเนินการ (ดูเอกสารสำหรับรายละเอียดทั้งหมด)


0

สำหรับ python 2.6+ ให้ใช้ gevent

 from gevent.subprocess import Popen, PIPE, STDOUT

 def call_sys(cmd, timeout):
      p= Popen(cmd, shell=True, stdout=PIPE)
      output, _ = p.communicate(timeout=timeout)
      assert p.returncode == 0, p. returncode
      return output

 call_sys('./t.sh', 2)

 # t.sh example
 sleep 5
 echo done
 exit 1

0

หลาม 2.7

import time
import subprocess

def run_command(cmd, timeout=0):
    start_time = time.time()
    df = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    while timeout and df.poll() == None:
        if time.time()-start_time >= timeout:
            df.kill()
            return -1, ""
    output = '\n'.join(df.communicate()).strip()
    return df.returncode, output

-1
import subprocess, optparse, os, sys, re, datetime, threading, time, glob, shutil, xml.dom.minidom, traceback

class OutputManager:
    def __init__(self, filename, mode, console, logonly):
        self.con = console
        self.logtoconsole = True
        self.logtofile = False

        if filename:
            try:
                self.f = open(filename, mode)
                self.logtofile = True
                if logonly == True:
                    self.logtoconsole = False
            except IOError:
                print (sys.exc_value)
                print ("Switching to console only output...\n")
                self.logtofile = False
                self.logtoconsole = True

    def write(self, data):
        if self.logtoconsole == True:
            self.con.write(data)
        if self.logtofile == True:
            self.f.write(data)
        sys.stdout.flush()

def getTimeString():
        return time.strftime("%Y-%m-%d", time.gmtime())

def runCommand(command):
    '''
    Execute a command in new thread and return the
    stdout and stderr content of it.
    '''
    try:
        Output = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]
    except Exception as e:
        print ("runCommand failed :%s" % (command))
        print (str(e))
        sys.stdout.flush()
        return None
    return Output

def GetOs():
    Os = ""
    if sys.platform.startswith('win32'):
        Os = "win"
    elif sys.platform.startswith('linux'):
        Os = "linux"
    elif sys.platform.startswith('darwin'):
        Os = "mac"
    return Os


def check_output(*popenargs, **kwargs):
    try:
        if 'stdout' in kwargs: 
            raise ValueError('stdout argument not allowed, it will be overridden.') 

        # Get start time.
        startTime = datetime.datetime.now()
        timeoutValue=3600

        cmd = popenargs[0]

        if sys.platform.startswith('win32'):
            process = subprocess.Popen( cmd, stdout=subprocess.PIPE, shell=True) 
        elif sys.platform.startswith('linux'):
            process = subprocess.Popen( cmd , stdout=subprocess.PIPE, shell=True ) 
        elif sys.platform.startswith('darwin'):
            process = subprocess.Popen( cmd , stdout=subprocess.PIPE, shell=True ) 

        stdoutdata, stderrdata = process.communicate( timeout = timeoutValue )
        retcode = process.poll()

        ####################################
        # Catch crash error and log it.
        ####################################
        OutputHandle = None
        try:
            if retcode >= 1:
                OutputHandle = OutputManager( 'CrashJob_' + getTimeString() + '.txt', 'a+', sys.stdout, False)
                OutputHandle.write( cmd )
                print (stdoutdata)
                print (stderrdata)
                sys.stdout.flush()
        except Exception as e:
            print (str(e))

    except subprocess.TimeoutExpired:
            ####################################
            # Catch time out error and log it.
            ####################################
            Os = GetOs()
            if Os == 'win':
                killCmd = "taskkill /FI \"IMAGENAME eq {0}\" /T /F"
            elif Os == 'linux':
                killCmd = "pkill {0)"
            elif Os == 'mac':
                # Linux, Mac OS
                killCmd = "killall -KILL {0}"

            runCommand(killCmd.format("java"))
            runCommand(killCmd.format("YouApp"))

            OutputHandle = None
            try:
                OutputHandle = OutputManager( 'KillJob_' + getTimeString() + '.txt', 'a+', sys.stdout, False)
                OutputHandle.write( cmd )
            except Exception as e:
                print (str(e))
    except Exception as e:
            for frame in traceback.extract_tb(sys.exc_info()[2]):
                        fname,lineno,fn,text = frame
                        print "Error in %s on line %d" % (fname, lineno)

นี่คือสิ่งที่น่ารังเกียจ
Corey Goldberg

-2

แค่พยายามเขียนสิ่งที่ง่ายกว่า

#!/usr/bin/python

from subprocess import Popen, PIPE
import datetime
import time 

popen = Popen(["/bin/sleep", "10"]);
pid = popen.pid
sttime = time.time();
waittime =  3

print "Start time %s"%(sttime)

while True:
    popen.poll();
    time.sleep(1)
    rcode = popen.returncode
    now = time.time();
    if [ rcode is None ]  and  [ now > (sttime + waittime) ] :
        print "Killing it now"
        popen.kill()

time.sleep (1) เป็นความคิดที่ไม่ดีมาก - ลองจินตนาการว่าคุณต้องการเรียกใช้คำสั่งมากมายที่ใช้เวลาประมาณ 0.002 วินาที คุณค่อนข้างจะรอในขณะที่การสำรวจความคิดเห็น () (ดูเลือกสำหรับ Linux epol แนะนำ :)
ddzialak
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.