시간 초과와 함께 모듈 '하위 프로세스'사용


325

다음은 stdout데이터를 반환하는 임의의 명령을 실행 하거나 0이 아닌 종료 코드에서 예외를 발생 시키는 Python 코드입니다.

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에서 실행되는 Python 프로그램에서 시간 초과를 구현 하는 가장 간단한 방법 은 무엇입니까 ?


2
이와 관련된 파이썬 이슈 트래커 항목 : bugs.python.org/issue5673
스리 Ratnakumar

10
Python2.x 에는 pypi.python.org/pypi/subprocess32 를 사용하십시오 . Python 3.x의 백 포트입니다. call () 및 wait ()에 대한 시간 종료 인수가 있습니다.
guettli

답변:


170

파이썬 3.3 이상에서 :

from subprocess import STDOUT, check_output

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

output 명령의 병합 된 stdout, stderr 데이터를 포함하는 바이트 문자열입니다.

check_output인상 CalledProcessError과는 달리 문제의 텍스트에 지정된대로 0이 아닌 종료 상태에 대한 proc.communicate()방법.

shell=True불필요하게 자주 사용되기 때문에 제거 했습니다. cmd실제로 필요한 경우 언제든지 다시 추가 할 수 있습니다 . 당신이 추가 할 경우 shell=True즉, 자식 프로세스의 급부상 경우 자신의 후손; check_output()제한 시간이 표시 한 것보다 훨씬 늦게 리턴 될 수 있습니다 ( 서브 프로세스 제한 시간 실패 참조) .

타임 아웃 기능은 subprocess323.2+ 서브 프로세스 모듈 의 백 포트를 통해 Python 2.x에서 사용할 수 있습니다 .


17
실제로 하위 프로세스 시간 초과 지원은 Python 2에서 사용하기 위해 유지하는 subprocess32 백 포트에 있습니다. pypi.python.org/pypi/subprocess32
gps

8
@gps Sridhar는 크로스 플랫폼 솔루션을 요청했지만 백 포트는 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_output3.5가 아닌 3.7 이후 사용 가능
jfs

205

나는 낮은 수준의 세부 사항에 대해 많이 모른다. 그러나 파이썬 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).

나는 창문에서 테스트하지 않았습니다. 그러나 예제 명령을 업데이트하는 것 외에도 thread.join 또는 process.terminate가 지원되지 않는다는 것을 문서에서 찾지 못했기 때문에 작동해야한다고 생각합니다.


16
+1 플랫폼 독립적입니다. 나는 이것을 Linux와 Windows 7 (cygwin과 plain windows python) 둘 다에서 실행했습니다. 세 경우 모두 예상대로 작동합니다.
phooji

7
네이티브 Popen kwargs를 전달하고 요지에 넣을 수 있도록 코드를 약간 수정했습니다. 이제 다용도를 사용할 준비가되었습니다. gist.github.com/1306188
커핏

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 클래스를 사용하여 단순화 할 수 있습니다 .

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
나는이 유닉스 기반 접근법을 좋아한다. 이상적으로는 (CreateProcess 및 Jobs를 사용하여) Windows 고유의 접근 방식과 이것을 결합하는 것이 이상적입니다. 그러나 현재 아래 솔루션은 간단하고 쉽고 간단합니다.
Sridhar Ratnakumar

3
휴대용 솔루션을 추가했습니다. 답변보기
flybywire

4
이 솔루션은 작동합니다 only_if signal.signal (signal.SIGALARM, alarm_handler) 메인 쓰레드에서 호출됩니다. 신호에 대한 설명서를 참조하십시오
volatilevoid

불행히도, mod_python, mod_perl 또는 mod_php와 같은 Apache 모듈의 컨텍스트에서 (Linux에서) 실행할 때 신호 및 경보 사용이 허용되지 않는 것으로 나타났습니다 (아파치 자체의 IPC 논리를 방해하기 때문에). 따라서 명령 시간을 초과하는 목표를 달성하기 위해 자식 프로세스를 시작한 다음 "자식 루프"를 작성하여 시계를보고 "자식 출력"을 모니터링하는 "잠자기"루프에 앉아야했습니다.
Peter

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
이것은 윈도우에서 작동하지 않으며 기능 순서가 반대로됩니다.
Hamish Grubijan

3
다른 처리기가 SIGALARM에 자신을 등록하고이 처리기가 "kill"상태가되기 전에 프로세스를 종료하는 경우 때때로 예외가 발생합니다. BTW, 훌륭한 레시피! 나는 이것을 사용하여 핸들링 래퍼를 멈추거나 충돌시키지 않고 지금까지 50,000 개의 버그 처리를 시작했습니다.
야로슬라프 불라

스레드 응용 프로그램에서 실행되도록 어떻게 수정할 수 있습니까? 나는 작업자 스레드 내에서 그것을 사용하려고 노력하고 있습니다ValueError: signal only works in main thread
wim

@Yaroslav Bulatov 정보 주셔서 감사합니다. 언급 된 문제를 처리하기 위해 추가 한 해결 방법은 무엇입니까?
jpswain

1
"try; catch"블록을 추가하면 코드 안에 있습니다. BTW, 장기적으로 이것은 하나의 SIGALARM 핸들러 만 설정할 수 있고 다른 프로세스는 재설정 할 수 있기 때문에 문제가되는 것으로 나타났습니다. 이 문제를 해결하는 한 가지 방법은 여기에 주어집니다 - stackoverflow.com/questions/6553423/...을
야로 슬라브 Bulatov을

18

sussudio 답변을 수정 했습니다 . 이제 수익을 기능 : ( returncode, stdout, stderr, timeout) - stdoutstderrUTF-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

이것은 모든 유스 케이스에 분명히 적용되는 것은 아니지만 간단한 스크립트를 다루는 경우 이길 수 없습니다.

homebrewmac 사용자 를 위해 coreutils에서 gtimeout으로도 제공됩니다 .


1
당신은 의미합니다 : proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). 거기에 timeout영업 이익 요청으로 Windows에서 명령은?
jfs

Windows에서는 Windows에서 bash 유틸리티를 허용하는 git bash 와 같은 응용 프로그램을 사용할 수 있습니다 .
Kaushik Acharya

@KaushikAcharya git bash를 사용하더라도 Python이 하위 프로세스를 호출하면 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

또 다른 옵션은 communi ()로 폴링하지 않고 stdout 차단을 방지하기 위해 임시 파일에 쓰는 것입니다. 이것은 다른 답변이없는 곳에서 저에게 효과적이었습니다. 예를 들어 창문에서.

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

불완전한 것 같습니다-임시 파일이란 무엇입니까?
spiderplant0

"Popen"호출에 "import tempfile", "import time"및 "shell = True"를 포함 시키십시오 ( "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 = None, shell = False, cwd = None, timeout = None, check = False, encoding = None, errors = None)

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 를 붙이는 것입니다. 입니다. comand가 너무 오래 걸리면 timelimit가 중지하고 Popen은 timelimit로 설정된 리턴 코드를 갖습니다. 128보다 크면 시간 제한이 프로세스를 종료했음을 의미합니다.

시간 초과 및 큰 출력 (> 64K)의 Python 하위 프로세스 참조


나는라는 비슷한 도구 사용 timeout- packages.ubuntu.com/search?keywords=timeout을 -하지만 둘 다 Windows의 작품, 그들은을하지 않는다?
Sridhar Ratnakumar


5

파이썬 2를 사용하고 있다면 시도해보십시오.

import subprocess32

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

1
아마 윈도우에서 작동하지 않는 초기 질문에 질문으로
장 - 프랑소와 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 () (현재 * nix에서 거의 모든 곳에서 사용 가능)를 사용하여 시간 초과 가능한 communi () meth를 만드는 방법에 대한 간단한 예제를 고려하십시오. 이것은 epoll / poll / kqueue로 작성할 수도 있지만 select.select () 변형이 좋은 예가 될 수 있습니다. 그리고 select.select () (속도 및 1024 max fds)의 주요 제한 사항은 작업에 적용 할 수 없습니다.

이것은 * nix에서 작동하며 스레드를 만들지 않고 신호를 사용하지 않으며 모든 스레드 (메인뿐만 아니라)에서 라우 킹 할 수 있으며 내 컴퓨터의 stdout (i5 2.3ghz)에서 250mb / s의 데이터를 읽을 수있을 정도로 빠릅니다.

통신 종료시 stdout / stderr에 참여하는 데 문제점이 있습니다. 프로그램 출력이 크면 메모리 사용량이 커질 수 있습니다. 그러나 더 작은 시간 초과로 communi ()을 여러 번 호출 할 수 있습니다.

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
이것은 문제의 유닉스 절반 만 해결합니다.
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

광범위하게 살펴 보지는 않았지만 ActiveState에서 찾은 이 데코레이터 는 이러한 종류의 작업에 매우 유용한 것으로 보입니다. 와 함께 subprocess.Popen(..., close_fds=True)적어도 파이썬에서 쉘 스크립팅을 할 준비가되었습니다.


이 데코레이터는 signal.alarm을 사용하며 Windows에서는 사용할 수 없습니다.
dbn

1

이 솔루션은 shell = True 인 경우 프로세스 트리를 종료하고, 매개 변수를 프로세스에 전달하거나 (또는 ​​그렇지 않음), 시간 종료를 가지며 콜백의 stdout, stderr 및 프로세스 출력을 가져옵니다 (kill_proc_tree에 psutil을 사용함). 이것은 jcollado를 포함하여 SO에 게시 된 여러 솔루션을 기반으로했습니다. jcollado의 답변에 Anson과 jradice의 의견에 대한 답변으로 게시. 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)

이것은 윈도우와 유닉스와 호환됩니다. "results"는 사전이며, "actionTaken"뿐만 아니라 앱의 리턴 인 "returnCode"(또는 종료 된 경우 없음)를 포함합니다. 프로세스가 정상적으로 완료된 경우 "SUBPROCESS2_PROCESS_COMPLETED"또는 조치에 따라 "SUBPROCESS2_PROCESS_TERMINATED"및 SUBPROCESS2_PROCESS_KILLED 마스크 (자세한 내용은 설명서 참조)가됩니다.


0

파이썬 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 초가 걸리는 많은 명령을 실행한다고 가정하십시오. poll () 동안 잠시 기다려야합니다 (Linux epol 권장 : select 참조)
ddzialak
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.