Використання модуля "підпроцес" з таймаутом


325

Ось код Python для запуску довільної команди, що повертає свої stdoutдані, або підняття виключення з ненульових кодів виходу:

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

communicate використовується для очікування виходу процесу:

stdoutdata, stderrdata = proc.communicate()

subprocessМодуль не підтримує тайм - ауту - здатність вбивати процес , що протікає в протягом більш ніж X кількості секунд - таким чином, communicateможе тривати вічно бігти.

Який найпростіший спосіб реалізувати тайм-аути в програмі Python, призначений для роботи в Windows та Linux?


2
Пов’язаний запис трекера випуску Python: bugs.python.org/issue5673
Sridhar Ratnakumar

10
Використовуйте pypi.python.org/pypi/subprocess32 для Python2.x. Це опора Python 3.x. У ньому є аргумент тайм-ауту для виклику () та wait ().
guettli

1
pypi.python.org/pypi/subprocess32 не працює в Windows :(
adrianX

Відповіді:


170

У Python 3.3+:

from subprocess import STDOUT, check_output

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

output це рядок байтів, який містить дані об'єднаного stdout, stderr даних команди.

check_outputпідвищує CalledProcessErrorненульовий статус виходу, як зазначено в тексті запитання на відміну від proc.communicate()методу.

Я видалив, shell=Trueтому що його часто використовують без потреби. Ви завжди можете додати його назад, якщо cmdдійсно цього вимагає. Якщо ви додасте, shell=Trueтобто, якщо дочірній процес породить власних нащадків; check_output()може повернутися набагато пізніше, ніж вказує час очікування .

Функція тайм- subprocess32аута доступна на Python 2.x через задній порт модуля 3,2 підпроцесу.


17
Дійсно, і підтримка часу закінчення підпроцеси існує в підпорті
gps

8
@gps Sridhar попросив рішення для крос-платформ, тоді як ваш портпорт підтримує лише POSIX: коли я його випробував, MSVC скаржився (очікував) на відсутність unistd.h :)
Shmil The Cat

Якщо вихід не потрібен, ви можете просто скористатися subprocess.call.
Кайл Гібсон

Починаючи з Python3.5, використовуйте subprocess.run () з capture_output = True та використовуйте параметр кодування для отримання виводу usefoul.
MKesper

1
@MKesper: 1- check_output()- кращий спосіб отримати вихід (він повертає результат безпосередньо, не ігнорує помилки, він доступний назавжди). 2- run()є більш гнучким, але run()ігнорує помилку за замовчуванням і вимагає додаткових кроків, щоб отримати результат 3- check_output()реалізований з точки зору,run() і тому він приймає більшість однакових аргументів. 4- ніт: capture_outputдоступний з 3.7, а не 3.5
jfs

205

Я не знаю багато про деталі низького рівня; але, враховуючи, що в python 2.6 API пропонує можливість чекати потоків та закінчувати процеси, що робити із запуском процесу в окремому потоці?

import subprocess, threading

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

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

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

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

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

Вихід цього фрагмента в моїй машині:

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

де видно, що в першому виконанні процес закінчився правильно (код повернення 0), а в другому - процес був припинений (код повернення -15).

Я не тестував у Windows; але, окрім оновлення прикладу команди, я думаю, що це повинно працювати, оскільки я не знайшов у документації нічого, що говорить про те, що thread.join або process.terminate не підтримується.


16
+1 За незалежність від платформи. Я запускав це на Linux і Windows 7 (cygwin та звичайний windows python) - працює як слід у всіх трьох випадках.
phooji

7
Я трохи змінив ваш код, щоб можна було передати рідні Popen kwargs і поставити його на суть. Тепер він готовий до використання багатоцільового призначення; gist.github.com/1306188
kirpit

2
Для всіх, у кого проблема @redice, це питання може допомогти. Коротше кажучи, якщо ви використовуєте shell = True, оболонка стає дочірнім процесом, який вбивається, і його команда (дитина дочірнього процесу) живе далі.
Ансон

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 Це має бути прийнятою відповіддю, оскільки не потрібно змінювати спосіб запуску процесу.
Дейв Брантон

1
Чому для цього потрібна лямбда? Не вдалось би використати зв'язаний метод p.kill без лямбда?
Danny Staple

//, Чи бажаєте ви включити приклад використання цього?
Натан Басанес

1
@tuk timer.isAlive()раніше timer.cancel()означає, що це закінчилося нормально
Чарльз

83

Якщо ви перебуваєте на Unix,

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

def alarm_handler(signum, frame):
    raise Alarm

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

3
Ну, мене цікавить кросплатформене рішення, яке працює принаймні на win / linux / mac.
Шрідхар Ратнакумар

1
Мені подобається цей підхід на основі Unix. В ідеалі, можна поєднувати це з підходом до Windows (використовуючи CreateProcess і Jobs).
Шрідхар Ратнакумар

3
Я додав портативне рішення, дивіться мою відповідь
flybywire

4
Це рішення буде працювати тільки_ff signal.signal (signal.SIGALARM, alarm_handler) викликається з основного потоку. Дивіться документацію щодо сигналу
летючий

На жаль, під час запуску (на Linux) у контексті модуля Apache (наприклад, mod_python, mod_perl або mod_php) я виявив забороненим використання сигналів та тривог (мабуть тому, що вони заважають логіці IPC Apache). Отже, щоб досягти мети встановити тайм-аут команди, мене змусили написати "батьківські петлі", які запускають дочірній процес, а потім сидіти в "сплячому" циклі, спостерігаючи за годинником (і, можливо, також контролюючи вихід дитини).
Пітер

44

Ось рішення Алекса Мартеллі як модуль із належним вбивством процесу. Інші підходи не працюють, оскільки вони не використовують proc.communicate (). Отже, якщо у вас є процес, який видає багато виводу, він заповнить свій вихідний буфер і блокується, поки ви щось з нього не прочитаєте.

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

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

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

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

3
Це не буде працювати у Windows, плюс порядок функцій змінюється.
Гаміш Грубіян

3
Це іноді призводить до винятку, коли інший обробник зареєструється на SIGALARM і вбиває процес, перш ніж цей дістанеться "вбити", додав обхід. До речі, чудовий рецепт! Я використовував це для запуску 50 000 баггі-процесів досі, не заморожуючи і не забиваючи обробну обгортку.
Ярослав Булатов

Як це можна змінити для запуску в програмі Threaded? Я намагаюся використовувати його з робочих ниток і отримуюValueError: signal only works in main thread
wim

@Yaroslav Bulatov Дякую за інформацію. Яке рішення ви додали для вирішення зазначеного питання?
jpswain

1
Щойно доданий блок "спробувати; зловити", він знаходиться всередині коду. BTW, в перспективі, це виявило мені проблеми, оскільки ви можете встановити лише один обробник SIGALARM, а інші процеси можуть скинути його. Одне рішення для цього наведено тут - stackoverflow.com/questions/6553423/…
Ярослав Булатов

18

Я змінив відповідь sussudio . Тепер функція повертає: ( returncode, stdout, stderr, timeout) - stdoutі stderrдекодується для UTF-8 рядка

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

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

18

здивований, ніхто не згадав про використання timeout

timeout 5 ping -c 3 somehost

Це, очевидно, не спрацює для кожного випадку використання, але якщо ви маєте справу з простим сценарієм, це важко перемогти.

Також доступний як gtimeout в coreutils через homebrewдля користувачів Mac.


1
ви мали в виду: proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). Чи є timeoutкоманда в Windows, як запитує ОП?
jfs

У Windows можна використовувати таку програму, як git bash, яка дозволяє bash утиліти в Windows.
Каушик Ачарія

@KaushikAcharya, навіть якщо ви використовуєте git bash, коли python викликає підпроцес, він буде працювати в Windows, отже, цей байпас не працюватиме.
Наман Чикара

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

// я думаю, що ОП шукає рішення для старшого Python.
Натан Басанес

11

Інший варіант - записати у тимчасовий файл, щоб запобігти блокуванню stdout, замість того, щоб опитуватись через communication (). Це працювало для мене там, де інших відповідей не було; наприклад на 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()

Здається неповним - що таке тимплейф?
spiderplant0

Включіть "тимчасовий імпорт файлу", "час імпорту" та "оболонку = вірно" всередині дзвінка "Popen" (остерігайтеся "shell = True")!
Едуардо Лусіо

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, error = None)

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

Він створює subprocess.TimeoutExpiredвиняток, коли закінчився час очікування.


6

Ось моє рішення, я використовував 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

Дія:

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

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

5

Я використовую рішення - префіксувати команду оболонки timelimit . Якщо команда триватиме занадто довго, timelimit зупинить її, і Popen матиме зворотний код, встановлений timelimit. Якщо це> 128, це означає, що timelimit вбив процес.

Дивіться також підпроцес python з таймаутом та великим виходом (> 64K)


Я використовую подібний інструмент під назвою timeout- пакети.ubuntu.com/search?keywords=timeout - але він не працює в Windows, чи не так?
Шрідхар Ратнакумар

5

Я додав рішення з нанизуванням jcolladoна мій модуль Python easyprocess .

Встановити:

pip install easyprocess

Приклад:

from easyprocess import Proc

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

Модуль easyprocess ( code.activestate.com/pypm/easyprocess ) працював для мене, навіть використовуючи його для багатопроцесорної обробки.
iChux

5

якщо ви використовуєте python 2, спробуйте

import subprocess32

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

1
Ймовірно, не працює над Windows, як задали у первинному запитанні
Жан-Франсуа Т.

5

Попередження команди Linux timeoutне є поганим вирішенням, і для мене це спрацювало.

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

Як я можу отримати роздруковані рядки під час виконання підпроцесу? - Покладені повідомлення повертаються підпроцесом.
Аммад

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, ви легко знайдете більш просте рішення:

Розглянемо цей простий приклад, як змусити timeoutable спілкуватись () meth, використовуючи select.select () (доступний майже всім на * nix зараз). Це також можна записати за допомогою epoll / poll / kqueue, але варіант select.select () може бути хорошим прикладом для вас. І основні обмеження select.select () (швидкість та 1024 макс. FDS) не застосовуються для вашої задачі.

Це працює під * nix, не створює потоки, не використовує сигналів, може бути запущено з будь-якого потоку (не тільки основного) і швидко вимагає зчитувати 250 мб / с даних з stdout на моїй машині (i5 2.3 ГГц).

Виникає проблема в з'єднанні stdout / stderr в кінці спілкування. Якщо у вас є величезний вихід програми, це може призвести до великого використання пам'яті. Але ви можете зателефонувати спілкуватися () кілька разів із меншими таймаутами.

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

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

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

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

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

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

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

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

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

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

        return (stdout, stderr)

2
Це стосується лише половини проблеми Unix.
Spaceghost

2

Це можна зробити за допомогою select

import subprocess
from datetime import datetime
from select import select

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


1

Хоча я ще не переглядав це детально, цей декоратор, який я знайшов в ActiveState, здається, дуже корисний для подібних речей. Поряд з subprocess.Popen(..., close_fds=True), принаймні, я готовий до сценаріїв оболонок у Python.


Цей декоратор використовує signal.alarm, який недоступний у Windows.
дбн

1

Це рішення вбиває дерево процесу у разі оболонки = True, передає параметри процесу (або ні), має тайм-аут і отримує stdout, stderr та вихід процесу зворотного виклику (він використовує psutil для kill_proc_tree). Це ґрунтувалося на кількох рішеннях, розміщених в SO, включаючи jcollado. Публікація у відповідь на коментарі Ансона та jradice у відповідь jcollado. Тестовано у Windows Srvr 2012 та Ubuntu 14.04. Зверніть увагу, що для Ubuntu потрібно змінити виклик parent.children (...) на parent.get_children (...).

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

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

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

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

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

1

Існує ідея підкласирувати клас Попен і розширити його за допомогою деяких простих методів декораторів. Назвемо це ExpirablePopen.

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


class ExpirablePopen(Popen):

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

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

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

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

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

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

            return result
        return wrapper

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


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

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

1

У мене виникла проблема, що я хотів припинити багатопотоковий підпроцес, якщо це зайняло більше часу, ніж заданий час очікування. Я хотів встановити тайм-аут Popen(), але це не вийшло. Потім я зрозумів, що Popen().wait()дорівнює, call()і тому у мене виникла ідея встановити тайм-аут у межах .wait(timeout=xxx)методу, який, нарешті, спрацював. Таким чином, я вирішив це так:

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

cores_for_parallelization = 4
timeout_time = 15  # seconds

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

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

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

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

if __name__ == '__main__':
    main()

0

На жаль, мене покладає дуже сувора політика щодо розкриття вихідного коду моїм роботодавцем, тому я не можу надати фактичний код. Але на мій смак найкращим рішенням є створення підкласу, який переосмислює Popen.wait()опитування, а не чекати нескінченно довго та Popen.__init__приймати параметр тайм-ауту. Як тільки ви це зробите, всі інші Popenметоди (які викликають wait) будуть працювати, як очікувалося, в тому числі communicate.


0

https://pypi.python.org/pypi/python-subprocess2 надає розширення до модуля підпроцесу, які дозволяють зачекати до певного періоду часу, інакше припиняються.

Отже, зачекати до 10 секунд, щоб процес закінчився, інакше вбити:

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

timeout =  10

results = pipe.waitOrTerminate(timeout)

Це сумісно як з Windows, так і з Unix. "results" - це словник, він містить "returnCode" - це повернення програми (або None, якщо його потрібно було вбити), а також "actionTaken". який буде "SUBPROCESS2_PROCESS_COMPLETED", якщо процес завершено нормально, або маска "SUBPROCESS2_PROCESS_TERMINATED" та SUBPROCESS2_PROCESS_KILLED, залежно від вжитих дій (детальну інформацію див. у документації)


0

для python 2.6+, використовуйте gevent

 from gevent.subprocess import Popen, PIPE, STDOUT

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

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

 # t.sh example
 sleep 5
 echo done
 exit 1

0

пітон 2.7

import time
import subprocess

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

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

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

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

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

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

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

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


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

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

        cmd = popenargs[0]

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

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

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

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

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

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

це гидота
Корі Голдберг

-2

Просто намагався написати щось простіше.

#!/usr/bin/python

from subprocess import Popen, PIPE
import datetime
import time 

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

print "Start time %s"%(sttime)

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

time.sleep (1) - дуже погана ідея - уявіть, що ви хочете виконати багато команд, які займуть приблизно 0,002 сек. Ви краще зачекати, поки опитування () (див. Виберіть, для Linux epol рекомендував :)
ddzialak
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.