Menggunakan modul 'subproses' dengan batas waktu

325

Berikut kode Python untuk menjalankan perintah sewenang-wenang mengembalikan stdoutdatanya, atau meningkatkan pengecualian pada kode keluar non-nol:

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

communicate digunakan untuk menunggu proses keluar:

stdoutdata, stderrdata = proc.communicate()

The subprocessmodul tidak mendukung batas waktu - kemampuan untuk membunuh proses yang berjalan selama lebih dari X jumlah detik - Oleh karena itu, communicatedapat mengambil selamanya untuk menjalankan.

Apa cara paling sederhana untuk mengimplementasikan timeout dalam program Python yang dimaksudkan untuk berjalan di Windows dan Linux?

Sridhar Ratnakumar
sumber
2
Entri pelacak isu Python terkait: bugs.python.org/issue5673
Sridhar Ratnakumar
10
Gunakan pypi.python.org/pypi/subprocess32 untuk Python2.x. Ini adalah backport dari Python 3.x. Ini memiliki argumen batas waktu untuk panggilan () dan menunggu ().
guettli
1
pypi.python.org/pypi/subprocess32 tidak berfungsi di Windows :(
adrianX

Jawaban:

170

Dengan Python 3.3+:

from subprocess import STDOUT, check_output

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

output adalah string byte yang berisi stdout, stderr data perintah yang digabungkan.

check_outputmeningkatkan CalledProcessErrorstatus keluar non-nol seperti yang ditentukan dalam teks pertanyaan ini tidak seperti proc.communicate()metode.

Saya telah menghapusnya shell=Truekarena sering digunakan secara tidak perlu. Anda selalu dapat menambahkannya kembali jika cmdmemang membutuhkannya. Jika Anda menambahkan shell=Trueyaitu, jika proses anak memunculkan keturunannya sendiri; check_output()dapat kembali lebih lama dari batas waktu yang ditunjukkan, lihat Kegagalan batas waktu proses .

Fitur batas waktu tersedia di Python 2.x melalui subprocess32backport modul subproses 3.2+.

jfs
sumber
17
Memang, dan dukungan waktu habis subproses ada di backport subprocess32 yang saya pertahankan untuk digunakan pada Python 2. pypi.python.org/pypi/subprocess32
gps
8
@ gps Sridhar meminta solusi lintas platform, sementara backport Anda hanya mendukung POSIX: ketika saya mencobanya, MSVC mengeluh (diharapkan) tentang kehilangan unistd.h :)
Shmil The Cat
Jika Anda tidak membutuhkan output, Anda bisa menggunakan subprocess.call.
Kyle Gibson
Sejak Python3.5, gunakan subprocess.run () dengan capture_output = True dan gunakan parameter encoding untuk mendapatkan output usefoul.
MKesper
1
@MKesper: 1- check_output()adalah cara yang disukai untuk mendapatkan output (mengembalikan output secara langsung, tidak mengabaikan kesalahan, tersedia sejak selamanya). 2- run()lebih fleksibel tetapi run()mengabaikan kesalahan secara default dan membutuhkan langkah-langkah tambahan untuk mendapatkan output 3- check_output()diimplementasikan dalam halrun() dan karenanya menerima sebagian besar argumen yang sama. 4- nit: capture_outputtersedia sejak 3,7, bukan 3,5
jfs
205

Saya tidak tahu banyak tentang detail level rendah; tetapi, mengingat bahwa dalam python 2.6 API menawarkan kemampuan untuk menunggu utas dan mengakhiri proses, bagaimana dengan menjalankan proses dalam utas terpisah?

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)

Output potongan ini di mesin saya adalah:

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

di mana dapat dilihat bahwa, dalam eksekusi pertama, proses selesai dengan benar (kode kembali 0), sedangkan yang kedua proses dihentikan (kode kembali -15).

Saya belum menguji di windows; tetapi, selain dari memperbarui contoh perintah, saya pikir itu harus berfungsi karena saya belum menemukan apa pun di dokumentasi yang mengatakan bahwa thread.join atau process.terminate tidak didukung.

jcollado
sumber
16
+1 Untuk menjadi platform independen. Saya sudah menjalankan ini di linux dan windows 7 (cygwin dan plain windows python) - berfungsi seperti yang diharapkan dalam ketiga kasus.
phooji
7
Saya telah sedikit memodifikasi kode Anda agar dapat lulus kwarg Popen asli dan meletakkannya di intinya. Sekarang siap untuk menggunakan multi guna; gist.github.com/1306188
kirpit
2
Bagi siapa pun yang memiliki masalah dengan @redice, pertanyaan ini dapat membantu. Singkatnya, jika Anda menggunakan shell = True, shell menjadi proses anak yang terbunuh, dan perintahnya (anak proses anak) hidup.
Anson
6
Jawaban ini tidak menyediakan fungsionalitas yang sama seperti aslinya karena tidak mengembalikan stdout.
stephenbez
2
thread.is_alive dapat menyebabkan kondisi balapan. Lihat ostricher.com/2015/01/python-spropro--with-timeout
ChaimKut
132

Jawaban jcollado dapat disederhanakan menggunakan kelas 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
sussudio
sumber
11
+1 untuk solusi portabel sederhana. Anda tidak perlu lambda:t = Timer(timeout, proc.kill)
jfs
3
+1 Ini harus menjadi jawaban yang diterima, karena tidak memerlukan cara perubahan proses diluncurkan.
Dave Branton
1
Mengapa itu membutuhkan lambda? Tidak bisakah metode terikat p. Dapat digunakan tanpa lambda di sana?
Danny Staple
//, Apakah Anda bersedia memasukkan contoh penggunaan ini?
Nathan Basanese
1
@tuk timer.isAlive()sebelum timer.cancel()berarti berakhir dengan normal
Charles
83

Jika Anda menggunakan 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
Alex Martelli
sumber
3
Yah, saya tertarik dengan solusi lintas-platform yang berfungsi setidaknya pada win / linux / mac.
Sridhar Ratnakumar
1
Saya suka pendekatan berbasis unix ini. Idealnya, seseorang akan menggabungkan ini dengan pendekatan khusus windows (menggunakan CreateProcess dan Jobs) .. tetapi untuk sekarang, solusi di bawah ini sederhana, mudah dan bekerja sejauh ini.
Sridhar Ratnakumar
3
Saya telah menambahkan solusi portabel, lihat jawaban saya
fly-by-wire
4
Solusi ini akan berfungsi only_if signal.signal (signal.SIGALARM, alarm_handler) dipanggil dari utas utama. Lihat dokumentasi untuk sinyal
volatilevoid
Sayangnya, ketika menjalankan (di linux) dalam konteks modul Apache (seperti mod_python, mod_perl, atau mod_php), saya telah menemukan penggunaan sinyal dan alarm untuk dianulir (mungkin karena mereka mengganggu logika IPC Apache sendiri). Jadi untuk mencapai tujuan pengaturan waktu perintah saya terpaksa menulis "loop orangtua" yang meluncurkan proses anak dan kemudian duduk dalam "tidur" y loop menonton jam (dan mungkin juga memantau output dari anak).
Peter
44

Berikut ini solusi Alex Martelli sebagai modul dengan proses pembunuhan yang tepat. Pendekatan lain tidak berfungsi karena mereka tidak menggunakan proc.communicate (). Jadi, jika Anda memiliki proses yang menghasilkan banyak output, itu akan mengisi buffer outputnya dan kemudian memblokir sampai Anda membaca sesuatu darinya.

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)
Björn Lindqvist
sumber
3
Ini tidak akan berfungsi di windows, ditambah urutan fungsi yang dibalik.
Hamish Grubijan
3
Ini kadang-kadang menghasilkan pengecualian ketika pawang lain mendaftarkan dirinya di SIGALARM dan membunuh proses sebelum yang ini untuk "membunuh", tambah work-around. BTW, resep bagus! Saya telah menggunakan ini untuk meluncurkan 50.000 proses kereta sejauh ini tanpa membekukan atau menabrak pembungkus penanganan.
Yaroslav Bulatov
Bagaimana ini bisa dimodifikasi untuk dijalankan di aplikasi Threaded? Saya mencoba menggunakannya dari dalam utas pekerja dan mendapatkanValueError: signal only works in main thread
wim
@Yaroslav Bulatov Terima kasih atas informasinya. Apa solusi yang Anda tambahkan untuk menangani masalah yang disebutkan?
jpswain
1
Baru saja menambahkan "coba; tangkap" blok, itu ada di dalam kode. BTW, dalam jangka panjang, ini ternyata memberi saya masalah karena Anda hanya dapat mengatur satu handler SIGALARM, dan proses lainnya dapat mengatur ulang. Salah satu solusi untuk ini diberikan di sini - stackoverflow.com/questions/6553423/…
Yaroslav Bulatov
18

Saya telah memodifikasi jawaban sussudio . Sekarang berfungsi kembali: ( returncode, stdout, stderr, timeout) - stdoutdan stderrditerjemahkan ke utf-8 string

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"]
Michal Zmuda
sumber
18

terkejut tidak ada yang disebutkan menggunakan timeout

timeout 5 ping -c 3 somehost

Ini tidak akan berfungsi untuk setiap kasus penggunaan jelas, tetapi jika Anda berurusan dengan skrip sederhana, ini sulit dikalahkan.

Juga tersedia sebagai gtimeout di coreutils via homebrewuntuk pengguna mac.

Karsten
sumber
1
maksud Anda: proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). Apakah ada timeoutperintah di Windows saat OP bertanya?
jfs
Di windows, seseorang dapat menggunakan aplikasi seperti git bash yang memungkinkan utilitas bash di Windows.
Kaushik Acharya
@ KaushikAcharya bahkan jika Anda menggunakan git bash, ketika python memanggil subproses itu akan berjalan pada Windows, maka bypass ini tidak akan berfungsi.
Naman Chikara
16

timeoutsekarang didukung oleh call()dan communicate()dalam modul subproses (pada Python3.3):

import subprocess

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

Ini akan memanggil perintah dan memunculkan eksepsi

subprocess.TimeoutExpired

jika perintah tidak selesai setelah 20 detik.

Anda kemudian dapat menangani pengecualian untuk melanjutkan kode Anda, seperti:

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

Semoga ini membantu.

James
sumber
ada jawaban yang ada yang menyebutkan timeoutparameter . Meskipun menyebutkannya sekali lagi tidak ada salahnya.
jfs
//, kupikir OP sedang mencari solusi untuk Python yang lebih lama.
Nathan Basanese
11

Pilihan lain adalah menulis ke file sementara untuk mencegah pemblokiran stdout alih-alih perlu polling dengan komunikasi (). Ini bekerja untuk saya di mana jawaban yang lain tidak; misalnya di 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()
Mat
sumber
Tampaknya tidak lengkap - apa itu tempfile?
spiderplant0
Sertakan "impor tempfile", "waktu impor" dan "shell = True" di dalam panggilan "Popen" (waspadalah dengan "shell = True")!
Eduardo Lucio
11

Saya tidak tahu mengapa itu tidak disebutkan tetapi karena Python 3.5, ada subprocess.runperintah universal baru (yang dimaksudkan untuk menggantikan check_call, check_output...) dan yang memiliki timeoutparameter juga.

subprocess.run (args, *, stdin = Tidak ada, input = Tidak ada, stdout = Tidak ada, stderr = Tidak ada, shell = Salah, cwd = Tidak ada, batas waktu = Tidak ada, centang = Salah, penyandian = Tidak ada, kesalahan = Tidak ada)

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

Ini menimbulkan subprocess.TimeoutExpiredpengecualian ketika batas waktu berakhir.

Jean-François Fabre
sumber
6

Ini solusi saya, saya menggunakan Thread and 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

Beraksi:

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

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

Solusi yang saya gunakan adalah untuk awalan perintah shell dengan timelimit . Jika perintah terlalu lama, timelimit akan menghentikannya dan Popen akan memiliki kode pengembalian yang ditetapkan oleh timelimit. Jika itu> 128, itu berarti timelimit membunuh proses.

Lihat juga subproses python dengan batas waktu dan keluaran besar (> 64K)

bortzmeyer
sumber
Saya menggunakan alat serupa yang disebut timeout- packages.ubuntu.com/search?keywords=timeout - tetapi tidak berfungsi di Windows, bukan?
Sridhar Ratnakumar
5

Saya menambahkan solusi dengan threading dari jcolladoke modul Python saya easyprocess saya .

Install:

pip install easyprocess

Contoh:

from easyprocess import Proc

# shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout
ponty
sumber
Modul easyprocess ( code.activestate.com/pypm/easyprocess ) bekerja untuk saya, bahkan menggunakannya dari multiprocessing.
iChux
5

jika Anda menggunakan python 2, cobalah

import subprocess32

try:
    output = subprocess32.check_output(command, shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
    print e
ThOong Ku
sumber
1
Mungkin tidak bekerja pada Windows, seperti yang ditanyakan dalam pertanyaan awal
Jean-Francois T.
5

Mempertahankan perintah Linux timeoutbukanlah solusi yang buruk dan berhasil bagi saya.

cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
Vikram Hosakote
sumber
Bagaimana saya bisa mencetak string put out selama eksekusi sub proses? - Pesan put dikembalikan dikembalikan oleh sub proses.
Ammad
3

Saya sudah menerapkan apa yang bisa saya kumpulkan dari beberapa di antaranya. Ini berfungsi di Windows, dan karena ini adalah wiki komunitas, saya pikir saya juga akan membagikan kode saya:

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

Kemudian dari kelas atau file lain:

        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()
joslinm
sumber
Sebenarnya, ini mungkin tidak berhasil. The terminate()tanda fungsi benang sebagai dihentikan, tetapi tidak benar-benar mengakhiri benang! Saya dapat memverifikasi ini di * nix, tetapi saya tidak memiliki komputer Windows untuk diuji.
dotancohen
2

Setelah Anda memahami proses penuh menjalankan mesin di * unix, Anda akan dengan mudah menemukan solusi yang lebih sederhana:

Pertimbangkan contoh sederhana ini bagaimana membuat komunikasi timeoutable () meth menggunakan select.select () (tersedia hampir semua tempat di * nix saat ini). Ini juga dapat ditulis dengan epoll / poll / kqueue, tetapi varian select.select () bisa menjadi contoh yang baik untuk Anda. Dan batasan utama select.select () (kecepatan dan 1024 maks fds) tidak dapat diterapkan untuk tugas Anda.

Ini berfungsi di bawah * nix, tidak membuat utas, tidak menggunakan sinyal, dapat diluncurkan dari utas apa pun (tidak hanya utama), dan cukup cepat untuk membaca 250mb / s data dari stdout pada mesin saya (i5 2.3ghz).

Ada masalah saat bergabung dengan stdout / stderr di akhir komunikasi. Jika Anda memiliki output program yang besar, ini dapat menyebabkan penggunaan memori yang besar. Tetapi Anda dapat memanggil berkomunikasi () beberapa kali dengan batas waktu yang lebih kecil.

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)
Vadim Fint
sumber
2
Ini hanya mengatasi setengah dari masalah Unix.
Spaceghost
2

Anda dapat melakukan ini menggunakan 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
dspeyer
sumber
1

Saya telah menggunakan proses killableproses dengan sukses di Windows, Linux dan Mac. Jika Anda menggunakan Cygwin Python, Anda akan memerlukan proses killableproses versi OSAF karena jika tidak, proses Windows asli tidak akan terbunuh.

Heikki Toivonen
sumber
Sepertinya proses killableproses tidak menambahkan batas waktu untuk panggilan Popen.communicate ().
Wim Coenen
1

Meskipun saya belum melihatnya secara luas, dekorator yang saya temukan di ActiveState ini tampaknya cukup berguna untuk hal semacam ini. Seiring dengan subprocess.Popen(..., close_fds=True), setidaknya saya siap untuk shell-scripting dengan Python.

Ehtesh Choudhury
sumber
Dekorator ini menggunakan signal.alarm, yang tidak tersedia di Windows.
dbn
1

Solusi ini membunuh pohon proses jika shell = True, meneruskan parameter ke proses (atau tidak), memiliki batas waktu dan mendapatkan stdout, stderr dan memproses output dari panggilan balik (menggunakan psutil untuk kill_proc_tree). Ini didasarkan pada beberapa solusi yang diposting di SO termasuk jcollado. Posting dalam menanggapi komentar oleh Anson dan jradice dalam jawaban jcollado. Diuji pada Windows Srvr 2012 dan Ubuntu 14.04. Harap dicatat bahwa untuk Ubuntu Anda perlu mengubah panggilan parent.children (...) ke 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()
Tomas
sumber
1

Ada ide untuk mensubkelas kelas Popen dan memperluasnya dengan beberapa dekorator metode sederhana. Sebut saja ExpiredPopen.

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 [email protected]', stdout=PIPE, timeout=1).communicate()
Alexander Yakovlev
sumber
1

Saya memiliki masalah yang ingin saya hentikan sub-proses multithreading jika perlu waktu lebih lama dari batas waktu yang diberikan. Saya ingin mengatur batas waktu Popen(), tetapi tidak berhasil. Kemudian, saya menyadari bahwa Popen().wait()itu sama dengan call()dan jadi saya punya ide untuk menetapkan batas waktu dalam .wait(timeout=xxx)metode, yang akhirnya berhasil. Jadi, saya memecahkannya dengan cara ini:

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()
sjor
sumber
0

Sayangnya, saya terikat oleh kebijakan yang sangat ketat tentang pengungkapan kode sumber oleh majikan saya, jadi saya tidak bisa memberikan kode yang sebenarnya. Tapi untuk seleraku solusi terbaik adalah membuat subclass overriding Popen.wait()untuk polling daripada menunggu tanpa batas waktu, dan Popen.__init__menerima parameter timeout. Setelah Anda melakukannya, semua Popenmetode lain (panggilan itu wait) akan berfungsi seperti yang diharapkan, termasuk communicate.

Patrick Narkinsky
sumber
0

https://pypi.python.org/pypi/python-subprocess2 memberikan ekstensi ke modul subproses yang memungkinkan Anda untuk menunggu periode waktu tertentu, jika tidak berakhir.

Jadi, untuk menunggu hingga 10 detik agar proses berakhir, jika tidak bunuh:

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

timeout =  10

results = pipe.waitOrTerminate(timeout)

Ini kompatibel dengan windows dan unix. "hasil" adalah kamus, ini berisi "returnCode" yang merupakan kembalinya aplikasi (atau tidak ada jika harus dibunuh), serta "actionTaken". yang akan menjadi "SUBPROCESS2_PROCESS_COMPLETED" jika proses selesai secara normal, atau topeng "SUBPROCESS2_PROCESS_TERMINATED" dan SUBPROCESS2_PROCESS_KILLED tergantung pada tindakan yang diambil (lihat dokumentasi untuk detail lengkap)

Tim Savannah
sumber
0

untuk python 2.6+, gunakan 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
whi
sumber
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
Joy Wang
sumber
-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)
PeterZhao
sumber
ini adalah kekejian
Corey Goldberg
-2

Hanya mencoba menulis sesuatu yang lebih sederhana.

#!/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()
Jabir Ahmed
sumber
time.sleep (1) adalah ide yang sangat buruk - bayangkan Anda ingin menjalankan banyak perintah yang akan memakan waktu sekitar 0,002sec. Anda sebaiknya menunggu sementara polling () (lihat pilih, untuk Linux epol direkomendasikan :)
ddzialak