Sử dụng mô-đun 'quy trình' với thời gian chờ


325

Dưới đây là đoạn mã Python để chạy một lệnh tùy ý trở về của nó stdoutdữ liệu, hoặc nâng một ngoại lệ về mã thoát khác không:

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

communicate được sử dụng để chờ cho quá trình để thoát:

stdoutdata, stderrdata = proc.communicate()

Các subprocessmô-đun không hỗ trợ thời gian chờ - khả năng để giết một quá trình chạy trong hơn X số giây - do đó, communicatecó thể mất mãi mãi để chạy.

Là gì đơn giản nhất cách để thực hiện timeout trong một chương trình Python có nghĩa là để chạy trên Windows và Linux?


2
Một mục theo dõi vấn đề Python có liên quan: bug.python.org/su5673
Sridhar Ratnakumar

10
Sử dụng pypi.python.org/pypi/sub process32 cho Python2.x. Nó là một cổng sau của Python 3.x. Nó có đối số thời gian chờ cho cuộc gọi () và chờ ().
guettli

1
pypi.python.org/pypi/sub process32 không hoạt động trên Windows :(
adrianX

Câu trả lời:


169

Trong Python 3.3+:

from subprocess import STDOUT, check_output

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

output là một chuỗi byte chứa dữ liệu xuất chuẩn, dữ liệu stderr được hợp nhất của lệnh.

check_outputlàm tăng CalledProcessErrortrạng thái thoát khác không như được chỉ định trong văn bản của câu hỏi không giống như proc.communicate()phương pháp.

Tôi đã gỡ bỏ shell=Truevì nó thường được sử dụng không cần thiết. Bạn luôn có thể thêm nó trở lại nếu cmdthực sự yêu cầu nó. Nếu bạn thêm shell=Truetức là, nếu quá trình con sinh ra con cháu của chính nó; check_output()có thể quay lại muộn hơn nhiều so với thời gian chờ chỉ ra, xem Lỗi hết thời gian của quy trình .

Tính năng hết thời gian có sẵn trên Python 2.x thông qua cổng sau subprocess32của mô đun quy trình con 3.2+.


17
Thật vậy, và hỗ trợ thời gian chờ của quy trình con tồn tại trong backport32 của tiến trình con mà tôi duy trì để sử dụng trên Python 2. pypi.python.org/pypi/sub process32
gps

8
@gps Sridhar yêu cầu giải pháp đa nền tảng, trong khi backport của bạn chỉ hỗ trợ POSIX: khi tôi dùng thử, MSVC đã phàn nàn (dự kiến) về việc thiếu unistd.h :)
Shmil The Cat

Nếu bạn không cần đầu ra, bạn chỉ có thể sử dụng sub process.call.
Kyle Gibson

Kể từ Python3.5, hãy sử dụng sub process.run () với captDefput = True và sử dụng tham số mã hóa để nhận đầu ra usefoul.
MKesper

1
@MKesper: 1- check_output()là cách ưa thích để nhận đầu ra (nó trả về đầu ra trực tiếp, không bỏ qua lỗi, nó có sẵn mãi mãi). 2- run()linh hoạt hơn nhưng run()bỏ qua lỗi theo mặc định và yêu cầu các bước bổ sung để có được đầu ra 3- check_output()được thực hiện theo các điều khoảnrun() và do đó nó chấp nhận hầu hết các đối số tương tự. 4- nit: capture_outputcó sẵn từ 3.7, không phải 3.5
jfs

205

Tôi không biết nhiều về các chi tiết cấp thấp; nhưng, do python 2.6 cung cấp khả năng chờ đợi các luồng và chấm dứt các tiến trình, vậy còn việc chạy tiến trình trong một luồng riêng biệt thì sao?

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)

Đầu ra của đoạn mã này trong máy của tôi là:

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

trong đó có thể thấy rằng, trong lần thực hiện đầu tiên, quy trình đã kết thúc chính xác (mã trả về 0), trong khi ở lần thứ hai, quy trình bị chấm dứt (mã trả về -15).

Tôi đã không thử nghiệm trong các cửa sổ; nhưng, ngoài việc cập nhật lệnh ví dụ, tôi nghĩ rằng nó sẽ hoạt động vì tôi không tìm thấy trong tài liệu bất cứ điều gì nói rằng thread.join hoặc process.terminate không được hỗ trợ.


16
+1 Để độc lập nền tảng. Tôi đã chạy cái này trên cả linux và windows 7 (cygwin và plain windows python) - hoạt động như mong đợi trong cả ba trường hợp.
phooji

7
Tôi đã sửa đổi mã của bạn một chút để có thể vượt qua các kwargs Popen bản địa và đưa nó vào ý chính. Bây giờ nó đã sẵn sàng để sử dụng đa mục đích; gist.github.com/1306188
kirpit

2
Đối với bất kỳ ai có vấn đề @redice đang gặp phải, câu hỏi này có thể giúp ích. Nói tóm lại, nếu bạn sử dụng shell = True, shell sẽ trở thành tiến trình con bị giết và lệnh của nó (con của tiến trình con) tiếp tục tồn tại.
Anson

6
Câu trả lời này không cung cấp chức năng tương tự của bản gốc vì nó không trả về thiết bị xuất chuẩn.
stephenbez 17/12/13

2
thread.is_alive có thể dẫn đến một điều kiện cuộc đua. Xem ostricher.com/2015/01/python-subprocess-with-timeout
ChaimKut

132

Câu trả lời của jcollado có thể được đơn giản hóa bằng cách sử dụng lớp phân luồng .

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 cho giải pháp di động đơn giản. Bạn không cần lambda:t = Timer(timeout, proc.kill)
jfs

3
+1 Đây phải là câu trả lời được chấp nhận, bởi vì nó không yêu cầu cách thức khởi động quy trình được thay đổi.
Dave Branton

1
Tại sao nó yêu cầu lambda? Không thể sử dụng phương pháp ràng buộc p.kill mà không có lambda ở đó?
Daniel Staple

//, Bạn có sẵn sàng đưa ra một ví dụ về việc sử dụng này không?
Nathan Basan

1
@tuk timer.isAlive()trước timer.cancel()có nghĩa là nó đã kết thúc bình thường
Charles

83

Nếu bạn đang dùng Unix,

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
Chà, tôi quan tâm đến một giải pháp đa nền tảng hoạt động ít nhất là trên win / linux / mac.
Sridhar Ratnakumar

1
Tôi thích cách tiếp cận dựa trên unix này. Lý tưởng nhất, người ta sẽ kết hợp điều này với một cách tiếp cận dành riêng cho cửa sổ (sử dụng CreatProcess và Jobs) .. nhưng hiện tại, giải pháp dưới đây rất đơn giản, dễ dàng và hiệu quả cho đến nay.
Sridhar Ratnakumar

3
Tôi đã thêm một giải pháp di động, xem câu trả lời của tôi
flybywire

4
Giải pháp này sẽ chỉ hoạt động_if signal.signal (signal.SIGALARM, alarm_handler) được gọi từ luồng chính. Xem tài liệu về tín hiệu
volatilevoid

Thật không may, khi chạy (trên linux) trong ngữ cảnh của mô-đun Apache (như mod_python, mod_perl hoặc mod_php), tôi đã thấy việc sử dụng tín hiệu và báo động không được phép (có lẽ vì chúng can thiệp vào logic IPC của chính Apache). Vì vậy, để đạt được mục tiêu hết thời gian ra lệnh, tôi đã buộc phải viết "các vòng lặp cha mẹ" để khởi chạy một tiến trình con và sau đó ngồi trong một vòng lặp "ngủ" y xem đồng hồ (và cũng có thể theo dõi đầu ra từ trẻ).
Peter

44

Đây là giải pháp của Alex Martelli như một mô-đun với quy trình tiêu diệt thích hợp. Các cách tiếp cận khác không hoạt động vì chúng không sử dụng Proc.c truyền thông (). Vì vậy, nếu bạn có một quy trình tạo ra nhiều đầu ra, nó sẽ điền vào bộ đệm đầu ra của nó và sau đó chặn cho đến khi bạn đọc một cái gì đó từ nó.

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
Điều này sẽ không hoạt động trên các cửa sổ, cộng với thứ tự các chức năng được đảo ngược.
Hamish Grubijan

3
Điều này đôi khi dẫn đến ngoại lệ khi một trình xử lý khác đăng ký chính nó trên SIGALARM và giết chết quá trình trước khi cái này được "giết", thêm vào đó. BTW, công thức tuyệt vời! Tôi đã sử dụng điều này để khởi chạy 50.000 quy trình lỗi cho đến nay mà không bị đóng băng hoặc làm hỏng trình bao bọc xử lý.
Yaroslav Bulatov

Làm thế nào điều này có thể được sửa đổi để chạy trong một ứng dụng Threaded? Tôi đang cố gắng sử dụng nó từ trong các luồng công nhân và nhận đượcValueError: signal only works in main thread
wim

@Yar Tư Bulatov Cảm ơn thông tin. Cách giải quyết bạn đã thêm để giải quyết vấn đề được đề cập là gì?
jpswain

1
Chỉ cần thêm khối "thử; bắt", nó nằm trong mã. Về lâu dài, BTW, điều này hóa ra gây ra sự cố cho tôi vì bạn chỉ có thể đặt một trình xử lý SIGALARM và các quy trình khác có thể đặt lại nó. Một giải pháp cho vấn đề này được đưa ra ở đây - stackoverflow.com/questions/6553423/
Kẻ

18

Tôi đã sửa đổi câu trả lời sussudio . Bây giờ hoạt động trở lại: ( returncode, stdout, stderr, timeout) - stdoutstderrđược giải mã để utf-8 chuỗi

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

ngạc nhiên không ai đề cập đến việc sử dụng timeout

timeout 5 ping -c 3 somehost

Điều này sẽ không phù hợp với mọi trường hợp sử dụng rõ ràng, nhưng nếu bạn xử lý một tập lệnh đơn giản, thì điều này thật khó để đánh bại.

Cũng có sẵn dưới dạng gtimeout trong coreutils thông qua homebrewcho người dùng mac.


1
ý bạn là : proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). Có timeoutlệnh nào trên Windows như OP yêu cầu không?
jfs

Trong windows, người ta có thể sử dụng ứng dụng như git bash cho phép các tiện ích bash trong Windows.
Kaushik Acharya

@KaushikAcharya ngay cả khi bạn sử dụng git bash, khi python gọi tiến trình con, nó sẽ chạy trên Windows, do đó bỏ qua này sẽ không hoạt động.
Naman Chikara

16

timeouthiện được hỗ trợ bởi call()communicate()trong mô-đun quy trình con (kể từ Python3.3):

import subprocess

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

Điều này sẽ gọi lệnh và đưa ra ngoại lệ

subprocess.TimeoutExpired

nếu lệnh không hoàn thành sau 20 giây.

Sau đó, bạn có thể xử lý ngoại lệ để tiếp tục mã của mình, đại loại như:

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

Hi vọng điêu nay co ich.


có một câu trả lời hiện có đề cập đến timeouttham số . Mặc dù nhắc đến nó một lần nữa sẽ không đau.
jfs

//, tôi nghĩ OP đang tìm giải pháp cho Python cũ hơn.
Nathan Basan

11

Một tùy chọn khác là ghi vào một tệp tạm thời để ngăn chặn chặn đầu ra thay vì cần thăm dò ý kiến ​​với giao tiếp (). Điều này làm việc cho tôi nơi mà các câu trả lời khác không; ví dụ trên 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()

Có vẻ không đầy đủ - tempfile là gì?
spiderplant0

Bao gồm "nhập tempfile", "thời gian nhập" và "shell = True" bên trong lệnh gọi "Popen" (hãy cẩn thận với "shell = True")!
Eduardo Lucio

11

Tôi không biết tại sao nó không được đề cập nhưng kể từ Python 3.5, có một subprocess.runlệnh phổ quát mới (có nghĩa là thay thế check_call, check_output...) và cũng có timeouttham số này.

sub process.run (args, *, stdin = none, input = none, stdout = none, stderr = none, shell = false, cwd = none, timeout = none, check = false, mã hóa = Không, lỗi = Không)

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

Nó đưa ra một subprocess.TimeoutExpiredngoại lệ khi hết thời gian.


6

Đây là giải pháp của tôi, tôi đã sử dụng Thread và Event:

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

Trong hành động:

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

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

5

Giải pháp tôi sử dụng là tiền tố lệnh shell với timensonit . Nếu quá trình comand mất quá nhiều thời gian, timensonit sẽ dừng nó và Popen sẽ có một mã trả về được thiết lập bởi timensonit. Nếu nó> 128, điều đó có nghĩa là timensonit đã giết quá trình.

Xem thêm quy trình con python với thời gian chờ và đầu ra lớn (> 64K)


Tôi sử dụng một công cụ tương tự có tên là timeout- tests.ubfox.com/search?keywords=timeout - nhưng không hoạt động trên Windows, phải không?
Sridhar Ratnakumar

5

Tôi đã thêm các giải pháp với luồng từ jcolladođể mô-đun Python tôi easyprocess .

Tải về:

pip install easyprocess

Thí dụ:

from easyprocess import Proc

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

Mô-đun easy process ( code.activestate.com/pypm/easy Process ) đã hoạt động với tôi, thậm chí sử dụng nó từ đa xử lý.
iChux

5

nếu bạn đang sử dụng python 2, hãy dùng thử

import subprocess32

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

1
Có lẽ không hoạt động trên Windows, như đã hỏi trong câu hỏi ban đầu
Jean-Francois T.

5

Chuẩn bị lệnh Linux timeoutkhông phải là một cách giải quyết tồi và nó hiệu quả với tôi.

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

Làm thế nào tôi có thể nhận được các chuỗi ra in ra trong quá trình thực hiện quy trình phụ? - Ra đặt tin nhắn được trả lại bởi quá trình phụ.
Ammad

3

Tôi đã thực hiện những gì tôi có thể thu thập từ một vài trong số này. Điều này hoạt động trong Windows và vì đây là wiki cộng đồng, tôi nghĩ rằng tôi cũng sẽ chia sẻ mã của mình:

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

Sau đó từ một lớp hoặc tệp khác:

        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()

Trên thực tế, điều này có thể không hoạt động. Các terminate()dấu hiệu chức năng a thread như chấm dứt, nhưng không thực sự chấm dứt thread! Tôi có thể xác minh điều này trong * nix, nhưng tôi không có máy tính Windows để kiểm tra.
dotancohen

2

Khi bạn hiểu toàn bộ quy trình chạy máy móc trong * unix, bạn sẽ dễ dàng tìm thấy giải pháp đơn giản hơn:

Xem xét ví dụ đơn giản này về cách thực hiện giao tiếp () meth thời gian chờ bằng cách sử dụng select.select () (có sẵn mọi thứ khác trên * nix hiện nay). Điều này cũng có thể được viết bằng epoll / poll / kqueue, nhưng biến thể select.select () có thể là một ví dụ tốt cho bạn. Và những hạn chế chính của select.select () (tốc độ và 1024 max fds) không thể áp dụng cho nhiệm vụ của bạn.

Điều này hoạt động dưới * nix, không tạo luồng, không sử dụng tín hiệu, có thể được xóa khỏi bất kỳ luồng nào (không chỉ chính) và đủ nhanh để đọc 250mb / s dữ liệu từ thiết bị xuất chuẩn trên máy của tôi (i5 2.3ghz).

Có một vấn đề trong việc tham gia stdout / stderr khi kết thúc giao tiếp. Nếu bạn có đầu ra chương trình lớn, điều này có thể dẫn đến việc sử dụng bộ nhớ lớn. Nhưng bạn có thể gọi truyền thông () nhiều lần với thời gian chờ nhỏ hơn.

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
Điều này chỉ giải quyết một nửa Unix của vấn đề.
Spaceghost

2

Bạn có thể làm điều này bằng cách sử dụng 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

Tôi đã sử dụng killable process thành công trên Windows, Linux và Mac. Nếu bạn đang sử dụng Cygwin Python, bạn sẽ cần phiên bản quy trình tiêu diệt của OSAF vì nếu không các quy trình Windows gốc sẽ không bị giết.


Có vẻ như killable Process không thêm thời gian chờ vào cuộc gọi Popen.c truyền thông ().
Wim Coenen

1

Mặc dù tôi đã không nhìn vào nó một cách rộng rãi, nhưng trang trí này tôi tìm thấy ở ActiveState dường như khá hữu ích cho loại điều này. Cùng với subprocess.Popen(..., close_fds=True), ít nhất tôi đã sẵn sàng cho shell-scripting trong Python.


Trình trang trí này sử dụng signal.alarm, không có sẵn trên Windows.
dbn

1

Giải pháp này giết chết cây quy trình trong trường hợp shell = True, truyền tham số cho tiến trình (hoặc không), có thời gian chờ và nhận đầu ra, stderr và đầu ra của quá trình gọi lại (nó sử dụng psutil cho kill_proc_tree). Điều này dựa trên một số giải pháp được đăng trong SO, bao gồm cả jcollado. Đăng bài để trả lời các bình luận của Anson và jradice trong câu trả lời của jcollado. Đã thử nghiệm trong Windows Srvr 2012 và Ubuntu 14.04. Xin lưu ý rằng đối với Ubuntu, bạn cần thay đổi lệnh gọi Parent.children (...) thành 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

Có một ý tưởng để phân lớp lớp Popen và mở rộng nó với một số trình trang trí phương thức đơn giản. Chúng ta hãy gọi nó là ExpishingPopen.

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

Tôi gặp vấn đề là tôi muốn chấm dứt một quy trình con đa luồng nếu mất nhiều thời gian hơn một khoảng thời gian chờ nhất định. Tôi muốn đặt thời gian chờ Popen(), nhưng nó không hoạt động. Sau đó, tôi nhận ra rằng Popen().wait()nó bằng call()và vì vậy tôi đã có ý tưởng đặt thời gian chờ trong .wait(timeout=xxx)phương thức, cuối cùng đã hoạt động. Vì vậy, tôi đã giải quyết nó theo cách này:

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

Thật không may, tôi bị ràng buộc bởi các chính sách rất nghiêm ngặt về việc tiết lộ mã nguồn của chủ nhân của tôi, vì vậy tôi không thể cung cấp mã thực tế. Nhưng theo sở thích của tôi, giải pháp tốt nhất là tạo một lớp con ghi đè Popen.wait()để thăm dò thay vì chờ đợi vô thời hạn và Popen.__init__chấp nhận tham số thời gian chờ. Khi bạn thực hiện điều đó, tất cả các Popenphương thức khác (cuộc gọi wait) sẽ hoạt động như mong đợi, bao gồm cả communicate.


0

https://pypi.python.org/pypi/python-sub process2 cung cấp các tiện ích mở rộng cho mô đun quy trình con cho phép bạn chờ đến một khoảng thời gian nhất định, nếu không thì chấm dứt.

Vì vậy, phải đợi tối đa 10 giây để quá trình kết thúc, nếu không thì giết:

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

timeout =  10

results = pipe.waitOrTerminate(timeout)

Điều này tương thích với cả windows và unix. "results" là một từ điển, nó chứa "returnCode" là sự trở lại của ứng dụng (hoặc Không có nếu nó phải bị giết), cũng như "actionTaken". sẽ là "SUBPROCESS2_PROCESS_COMPLETED" nếu quá trình hoàn thành bình thường hoặc mặt nạ "SUBPROCESS2_PROCESS_TERMINATED" và SUBPROCESS2_PROCESS_KILLED tùy thuộc vào hành động được thực hiện (xem tài liệu để biết chi tiết đầy đủ)


0

đối với python 2.6+, hãy sử dụng 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

trăn 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)

đây là một điều kinh tởm
Corey Goldberg

-2

Chỉ cố gắng để viết một cái gì đó đơn giản hơn.

#!/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.s ngủ (1) là ý tưởng rất tệ - hãy tưởng tượng bạn muốn chạy nhiều lệnh sẽ mất khoảng 0,002 giây. Bạn nên chờ đợi trong khi thăm dò ý kiến ​​() (xem chọn, đối với Linux epol được đề xuất :)
ddzialak
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.