'Alt süreç' modülünü zaman aşımı ile kullanma


325

stdoutVerilerini döndüren rasgele bir komut çalıştırmak veya sıfır olmayan çıkış kodlarında bir istisna oluşturmak için Python kodu :

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

communicate işlemin çıkmasını beklemek için kullanılır:

stdoutdata, stderrdata = proc.communicate()

subprocessModül zaman aşımı desteklemez - saniyelik daha X'ten sayısı için çalışan bir süreç öldürme yeteneğine - bu nedenle communicateçalıştırmak için sonsuza sürebilir.

Windows ve Linux üzerinde çalışmak üzere bir Python programında zaman aşımlarını uygulamanın en basit yolu nedir ?


2
İlgili bir Python sorunu izleyicisi girişi: bugs.python.org/issue5673
Sridhar Ratnakumar

10
Python2.x için pypi.python.org/pypi/subprocess32 komutunu kullanın . Python 3.x'in bir backport'udur. Call () ve wait () için zaman aşımı bağımsız değişkenine sahiptir.
guettli

1
pypi.python.org/pypi/subprocess32 Windows'ta çalışmıyor :(
adrianX

Yanıtlar:


170

Python 3.3 ve üzeri sürümlerde:

from subprocess import STDOUT, check_output

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

output komutun birleştirilmiş stdout'u, stderr verilerini içeren bir bayt dizesidir.

check_outputyöntemin CalledProcessErroraksine, metnin metninde belirtildiği gibi sıfırdan farklı çıkış durumunu yükseltir proc.communicate().

Kaldırdım shell=Trueçünkü çoğu zaman gereksiz yere kullanılıyor. cmdGerçekten gerektiriyorsa her zaman geri ekleyebilirsiniz . Örneğin eklerseniz shell=True, çocuk süreci kendi torunlarını doğurursa; check_output()zaman aşımının gösterilenden çok daha sonra geri dönebilir, bkz. Alt işlem zaman aşımı hatası .

Zaman aşımı özelliği, Python subprocess322.x'te 3.2+ alt işlem modülünün backport'u üzerinden kullanılabilir .


17
Gerçekten de, Python 2'de kullanmak için sürdürdüğüm subprocess32 backport'ta alt işlem zaman aşımı desteği var. Pypi.python.org/pypi/subprocess32
gps

8
@gps Sridhar çapraz platform çözümü istedi, backport'unuz sadece POSIX'i destekledi: denediğimde MSVC eksik unistd.h'den şikayet etti (bekleniyor) :)
Shmil The Cat

Çıktıya ihtiyacınız yoksa, sadece subprocess.call dosyasını kullanabilirsiniz.
Kyle Gibson

Python3.5'ten beri capture_output = True ile subprocess.run () kullanın ve usefoul çıktısını almak için kodlama parametresini kullanın.
MKesper

1
@MKesper: 1- check_output()çıktı almanın tercih edilen yoludur (çıktıyı doğrudan döndürür, hataları yok saymaz, sonsuza dek kullanılabilir). 2- run()daha esnektir ancak run()varsayılan olarak hatayı yok sayar ve çıktı almak için ek adımlar gerektirir 3- check_output()açısından geçerlidirrun() ve bu nedenle aynı argümanların çoğunu kabul eder. 4 - nit: capture_output
3,5'ten

205

Düşük seviye detaylar hakkında fazla bir şey bilmiyorum; ancak, python 2.6'da API'nın iş parçacıklarını beklemesi ve işlemleri sonlandırma yeteneği sunduğu göz önüne alındığında, işlemi ayrı bir iş parçacığında çalıştırmaya ne dersiniz?

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)

Bu snippet'in makinemdeki çıktısı:

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

burada, ilk yürütmede, işlemin doğru şekilde tamamlandığı (dönüş kodu 0), ikincisinde ise işlemin sonlandırıldığı (dönüş kodu -15) görülebilir.

Pencerelerde test etmedim; ancak, örnek komutu güncellemenin yanı sıra, thread.join veya process.terminate komutunun desteklenmediğini söyleyen bir şey bulamadığım için işe yaradığını düşünüyorum.


16
+1 Platformdan bağımsız olduğu için. Ben hem linux hem de windows 7 (cygwin ve düz windows python) üzerinde çalıştım - her üç durumda da beklendiği gibi çalışır.
phooji

7
Yerli Popen kwarg'ları geçip gist'e koymak için kodunuzu biraz değiştirdim. Artık çok amaçlı kullanıma hazır; gist.github.com/1306188
kirpit

2
@Redice'nin sorunu olan herkes için bu soru yardımcı olabilir. Kısacası shell = True kullanırsanız, kabuk öldürülen alt süreç haline gelir ve komutu (alt sürecin alt öğesi) devam eder.
Anson

6
Bu yanıt, stdout döndürmediği için orijinalin aynı işlevselliğini sağlamaz.
stephenbez

2
thread.is_alive bir yarış durumuna yol açabilir. Bkz ostricher.com/2015/01/python-subprocess-with-timeout
ChaimKut

132

jcollado'nun cevabı, threading.Timer sınıfı kullanılarak basitleştirilebilir :

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
Basit taşınabilir çözüm için +1. Şunlar gerekmez lambda:t = Timer(timeout, proc.kill)
jfs

3
+1 Bu, kabul edilen cevap olmalıdır, çünkü sürecin başlatılma şeklinin değiştirilmesini gerektirmez.
Dave Branton

1
Neden lambda gerektiriyor? P.kill bağlı yöntem orada lambda olmadan kullanılamaz mı?
Danny Staple

//, bunun kullanımına bir örnek eklemek ister misiniz?
Nathan Basanese

1
@tuk daha timer.isAlive()önce timer.cancel()normal bittiği anlamına gelir
Charles

83

Unix kullanıyorsanız,

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
En azından win / linux / mac üzerinde çalışan bir çapraz platform çözümü ile ilgileniyorum.
Sridhar Ratnakumar

1
Bu unix tabanlı yaklaşımı seviyorum. İdeal olarak, bunu pencereye özgü bir yaklaşımla (CreateProcess ve Jobs kullanarak) birleştiririz. Ancak şimdilik, aşağıdaki çözüm basit, kolay ve şimdiye kadar çalışıyor.
Sridhar Ratnakumar

3
Taşınabilir bir çözüm ekledim, cevabımı görün
flybywire

4
Bu çözüm ana iş parçacığından only_if signal.signal (signal.SIGALARM, alarm_handler) olarak adlandırılır. Sinyal için belgelere bakın
volatilevoid

Ne yazık ki, (linux üzerinde) bir Apache modülü bağlamında (mod_python, mod_perl veya mod_php gibi) çalışırken, izin verilmeyen sinyal ve alarmların kullanımını buldum (muhtemelen Apache'nin kendi IPC mantığına müdahale ettikleri için). Bu nedenle, bir komutun zaman aşımına uğratma amacına ulaşmak için, bir alt süreci başlatan ve daha sonra saati izleyen bir "uyku" y döngüsünde oturmak zorunda kaldım (ve muhtemelen çocuktan gelen çıkışı da izledim).
Peter

44

İşte Alex Martelli'nin uygun süreç öldürme modülü olarak çözümü. Diğer yaklaşımlar işe yaramaz çünkü proc.communicate () kullanmazlar. Bu nedenle, çok sayıda çıktı üreten bir işleminiz varsa, çıktı arabelleğini dolduracak ve siz ondan bir şey okuyana kadar engelleyecektir.

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
Bu pencerelerde çalışmaz, ayrıca işlevlerin sırası tersine çevrilir.
Hamish Grubijan

3
Bu bazen, başka bir işleyici SIGALARM'a kendini kaydettirdiğinde ve bu işi "öldürmeye" başlamadan önce süreci öldürdüğünde istisna ile sonuçlanır. BTW, harika bir tarif! Bunu şimdiye kadar taşıma ambalajını dondurmadan veya çökmeden 50.000 buggy işlemi başlatmak için kullandım.
Yaroslav Bulatov

Bu, Dişli bir uygulamada çalışacak şekilde nasıl değiştirilebilir? Ben işçi iş parçacığı içinde kullanmaya ve almaya çalışıyorumValueError: signal only works in main thread
wim

@Yaroslav Bulatov Bilgi için teşekkürler. Belirtilen sorunla başa çıkmak için eklediğiniz geçici çözüm neydi?
jpswain

1
Sadece "try; catch" bloğunu ekledim, kodun içinde. BTW, uzun vadede, bana sadece bir SIGALARM işleyicisi ayarlayabildiğiniz ve diğer işlemler onu sıfırlayabildiğinden, bana sorun verdi. Bunun bir çözümü burada verilmiştir - stackoverflow.com/questions/6553423/…
Yaroslav Bulatov

18

Sussudio cevabını değiştirdim . Şimdi getirileri işlev: ( returncode, stdout, stderr, timeout) - stdoutve stderrutf-8 dizeye deşifre edilir

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

Kimse kullanarak bahsetti şaşırttı timeout

timeout 5 ping -c 3 somehost

Bu, her kullanım durumu için açık bir şekilde işe yaramayacaktır, ancak basit bir komut dosyasıyla uğraşmak, bunu yenmek zordur.

Ayrıca homebrewmac kullanıcıları için coreutils içinde gtimeout olarak da mevcuttur .


1
Şunu: proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). timeoutWindows'da OP'nin istediği gibi bir komut var mı ?
jfs

Pencerelerde Windows'da bash yardımcı programlarına izin veren git bash gibi bir uygulama kullanılabilir .
Kaushik Acharya

@KaushikAcharya git bash kullansanız bile, python alt işlemi çağırdığında Windows üzerinde çalışacaktır, bu nedenle bu bypass çalışmaz.
Naman Chikara

16

timeoutartık (Python3.3'ten itibaren) alt işlem modülü tarafından call()ve communicate()alt süreçte desteklenmektedir :

import subprocess

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

Bu komutu çağırır ve istisnayı artırır

subprocess.TimeoutExpired

komut 20 saniye sonra bitmezse.

Daha sonra, kodunuza devam etmek için istisnayı işleyebilirsiniz:

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

Bu yardımcı olur umarım.


timeoutparametreden bahseden mevcut bir cevap var . Bir kez daha bahsetmek acı vermezdi.
jfs

//, bence OP eski Python için bir çözüm arıyor.
Nathan Basanese

11

Başka bir seçenek, stdout'un engellenmesini önlemek için, iletişim () ile yoklamak yerine geçici bir dosyaya yazmaktır. Diğer cevapların olmadığı yerde bu benim için çalıştı; örneğin pencerelerde.

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

Eksik görünüyor - geçici dosya nedir?
Örümcek Adam0

"Popen" çağrısının içine "import tempfile", "import time" ve "shell = True" ifadelerini ekleyin ("shell = True" ile dikkat edin)!
Eduardo Lucio

11

Neden bahsetmiyorum bilmiyorum ama Python 3.5 beri, yeni bir subprocess.runevrensel komut var (yerine geçecek check_call, check_output...) ve hangi timeoutparametre de var.

subprocess.run (args, *, stdin = Yok, giriş = Yok, stdout = Yok, stderr = Yok, kabuk = Yanlış, cwd = Yok, zaman aşımı = Yok, kontrol = Yanlış, kodlama = Yok, hatalar = Yok)

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

subprocess.TimeoutExpiredZaman aşımı süresi dolduğunda bir istisna oluşturur.


6

İşte benim çözüm, Konu ve Olay kullanıyordum:

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

Eylemde:

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

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

5

Kullandığım çözüm shell komutunun önüne timelimit eklemek . Komut çok uzun sürerse, timelimit durduracak ve Popen'in timelimit tarafından ayarlanmış bir dönüş kodu olacaktır. 128'den büyükse, timelimit süreci öldürdü demektir.

Ayrıca bkz. Zaman aşımı ve büyük çıkışlı python alt süreci (> 64K)


timeout- Packages.ubuntu.com/search?keywords=timeout - adlı benzer bir araç kullanıyorum, ancak ikisi de Windows'ta çalışmıyor, değil mi?
Sridhar Ratnakumar


5

python 2 kullanıyorsanız, bir deneyin

import subprocess32

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

1
Muhtemelen ilk soruda sorulduğu gibi Windows'ta çalışmıyor
Jean-Francois T.

5

Linux komutunun timeouthazırlanması kötü bir çözüm değildir ve benim için çalıştı.

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

Alt işlem yürütme sırasında çıkış dizeleri çıktısını nasıl alabilirim? - Çıkış mesajları alt süreç tarafından döndürülür.
Ammad

3

Bunlardan birkaçından toplayabileceğimleri uyguladım. Bu Windows'da çalışır ve bu bir topluluk wiki olduğundan, kodumu da paylaşacağımı anlıyorum:

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

Sonra başka bir sınıftan veya dosyadan:

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

Aslında, bu muhtemelen işe yaramıyor. terminate()Fonksiyon işaretleri sonlandırıldı, ama aslında iş parçacığı sonlandırmak için de bu durum iplik! Bunu * nix'te doğrulayabilirim, ancak üzerinde test edilecek bir Windows bilgisayarım yok.
dotancohen

2

* Unix'te tam işlem çalışan makineleri anladıktan sonra, daha basit bir çözüm bulacaksınız:

Select.select () (günümüzde * nix'te hemen hemen her yerde kullanılabilir) kullanarak zaman aşımına tabi iletişim () yönteminin nasıl yapılacağını bu basit örneği düşünün. Bu, epoll / poll / kqueue ile de yazılabilir, ancak select.select () varyantı sizin için iyi bir örnek olabilir. Ve select.select () (speed ve 1024 max fds) gibi büyük sınırlamalar göreviniz için geçerli değildir.

Bu, * nix altında çalışır, iş parçacıkları oluşturmaz, sinyaller kullanmaz, herhangi bir iş parçacığından (yalnızca ana değil) lauch edilebilir ve makinemdeki stdout'tan 250mb / s veri okumak için yeterince hızlıdır (i5 2.3ghz).

İletişimin sonunda stdout / stderr'e katılmada bir sorun var. Eğer büyük bir program çıktısı varsa, bu büyük bellek kullanımına yol açabilir. Ancak, daha küçük zaman aşımları ile birkaç kez contac () öğesini çağırabilirsiniz.

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
Bu yalnızca sorunun Unix'in yarısını ele alır.
Spaceghost

2

Bunu kullanarak yapabilirsiniz 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

Her ne kadar kapsamlı bir şekilde bakmamış olsam da, ActiveState'de bulduğum bu dekoratör bu tür şeyler için oldukça yararlı görünüyor. Bununla birlikte subprocess.Popen(..., close_fds=True), en azından Python'da kabuk komut dosyası oluşturmaya hazırım.


Bu dekoratör, Windows'ta bulunmayan signal.alarm kullanır.
dbn

1

Bu çözüm, shell = True durumunda işlem ağacını öldürür, parametreyi işleme geçirir (veya geçirmez), bir zaman aşımına sahiptir ve geri çağrının stdout, stderr ve işlem çıktısını alır (kill_proc_tree için psutil kullanır). Bu, jcollado'lar dahil SO'da yayınlanan birkaç çözüme dayanıyordu. Jcollado'nun cevabında Anson ve jradice tarafından yapılan yorumlara yanıt olarak yayın. Windows Srvr 2012 ve Ubuntu 14.04'te test edilmiştir. Ubuntu için parent.children (...) çağrısını parent.get_children (...) olarak değiştirmeniz gerektiğini lütfen unutmayın.

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 sınıfını alt sınıflandırma ve bazı basit yöntem dekoratörleri ile genişletme fikri vardır. Buna ExpirablePopen diyelim.

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

Belirli bir zaman aşımı süresinden daha uzun sürdüyse, çok iş parçacıklı bir alt işlemi sonlandırmak istediğim bir sorun vardı. Ben bir zaman aşımı ayarlamak istedim Popen(), ama işe yaramadı. Sonra, Popen().wait()bunun eşit olduğunu fark ettim call()ve bu yüzden .wait(timeout=xxx)nihayet işe yarayan yöntem içinde bir zaman aşımı belirleme fikrim vardı . Böylece, bu şekilde çözdüm:

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

Ne yazık ki, işverenim tarafından kaynak kodunun açıklanması konusunda çok sıkı politikalara bağlıyım, bu yüzden gerçek kodu sağlayamıyorum. Ama benim zevkime göre en iyi çözüm, Popen.wait()süresiz olarak beklemek yerine ankete göre geçersiz kılınan bir alt sınıf yaratmak ve Popen.__init__bir zaman aşımı parametresini kabul etmektir. Bunu yaptıktan sonra, diğer tüm Popenyöntemler (çağrı wait), beklendiği gibi çalışacaktır communicate.


0

https://pypi.python.org/pypi/python-subprocess2 , alt işlem modülüne belirli bir süre beklemenizi sağlayan uzantılar sağlar, aksi takdirde sonlandırılır.

Bu nedenle, sürecin sona ermesi için 10 saniye kadar beklemek, aksi takdirde öldürmek:

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

timeout =  10

results = pipe.waitOrTerminate(timeout)

Bu hem windows hem de unix ile uyumludur. "results" bir sözlüktür, uygulamanın dönüşü olan "returnCode" (veya öldürülmesi gerekiyorsa None) ve "actionTaken" içerir. bu işlem normal şekilde tamamlandıysa "SUBPROCESS2_PROCESS_COMPLETED" veya alınan eyleme bağlı olarak "SUBPROCESS2_PROCESS_TERMINATED" ve SUBPROCESS2_PROCESS_KILLED maskesi (tüm ayrıntılar için belgelere bakın)


0

python 2.6+ için gevent kullanın

 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

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

Bu bir iğrenç
Corey Goldberg

-2

Sadece daha basit bir şeyler yazmaya çalışıyordu.

#!/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) çok kötü bir fikirdir - yaklaşık 0.002 saniye sürecek birçok komutu çalıştırmak istediğinizi düşünün. Anket () sırasında seçim yapmayı tercih etmelisiniz (bkz. Linux epol için önerilir :)
ddzialak
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.