Usando o módulo 'subprocesso' com tempo limite

325

Aqui está o código Python para executar um comando arbitrário retornando seus stdoutdados ou gerar uma exceção em códigos de saída diferentes de zero:

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

communicate é usado para aguardar a saída do processo:

stdoutdata, stderrdata = proc.communicate()

O subprocessmódulo não suporta tempo limite - capacidade de interromper um processo em execução por mais de X número de segundos - portanto,communicate pode levar uma eternidade para ser executada.

Qual é a maneira mais simples de implementar tempos limite em um programa Python para rodar no Windows e Linux?

Sridhar Ratnakumar
fonte
2
A Python item correspondente issue tracker: bugs.python.org/issue5673
Sridhar Ratnakumar
10
Use pypi.python.org/pypi/subprocess32 para Python2.x. É um backport do Python 3.x. Ele tem o argumento de tempo limite para call () e wait ().
guettli
1
O pypi.python.org/pypi/subprocess32 não funciona no Windows :(
adrianX

Respostas:

170

No Python 3.3 ou superior:

from subprocess import STDOUT, check_output

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

output é uma sequência de bytes que contém os dados stdout e stderr mesclados do comando.

check_outputaumenta CalledProcessErrorno status de saída diferente de zero, conforme especificado no texto da pergunta, diferenteproc.communicate() método.

Eu removi shell=Trueporque muitas vezes é usado desnecessariamente. Você sempre pode adicioná-lo de volta, se cmdnecessário. Se você adicionar shell=True, isto é, se o processo filho gerar seus próprios descendentes; check_output()pode retornar muito mais tarde do que o tempo limite indica, consulte Falha no tempo limite do subprocesso .

O recurso de tempo limite está disponível no Python 2.x através do subprocess32backport do módulo do subprocesso 3.2+.

jfs
fonte
17
Na verdade, e apoio de tempo limite subprocesso existe no backport subprocess32 que mantenho para uso em Python 2. pypi.python.org/pypi/subprocess32
gps
8
@gps Sridhar pediu para solução de plataforma cruzada, enquanto o backport suporta apenas POSIX: quando eu tentei sair, MSVC reclamou (esperado) sobre unistd.h faltando :)
Shmil The Cat
Se você não precisar da saída, basta usar o subprocess.call.
Kyle Gibson
Desde o Python3.5, use subprocess.run () com capture_output = True e use o parâmetro de codificação para obter uma saída útil.
MKesper
1
@MKesper: 1- check_output()é a maneira preferida de obter saída (ela retorna a saída diretamente, não ignora erros, está disponível desde sempre). 2- run()é mais flexível, mas run()ignora o erro por padrão e requer etapas adicionais para obter a saída 3- check_output()é implementado em termos derun() e, portanto, aceita a maioria dos mesmos argumentos. 4- nit: capture_outputestá disponível desde 3.7, não 3.5
jfs
205

Não sei muito sobre os detalhes de baixo nível; mas, como no python 2.6 a API oferece a capacidade de aguardar threads e finalizar processos, que tal executar o processo em um thread separado?

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)

A saída desse trecho na minha máquina é:

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

onde se percebe que, na primeira execução, o processo foi finalizado corretamente (código de retorno 0), enquanto na segunda o processo foi finalizado (código de retorno -15).

Eu não testei no Windows; mas, além de atualizar o comando de exemplo, acho que deve funcionar, pois não encontrei na documentação nada que diga que thread.join ou process.terminate não é suportado.

jcollado
fonte
16
+1 Por ser independente de plataforma. Eu executei isso no linux e no windows 7 (cygwin e python do windows simples) - funciona como esperado nos três casos.
Phooji
7
Modifiquei um pouco o seu código para poder passar os kengs nativos do Popen e colocá-lo no gist. Agora está pronto para usar multiuso; gist.github.com/1306188
kirpit (
2
Para quem está com o problema que @redice estava tendo, esta pergunta pode ajudar. Em resumo, se você usar shell = True, o shell se torna o processo filho que é morto e seu comando (filho do processo filho) permanece vivo.
Anson
6
Esta resposta não fornece a mesma funcionalidade do original, pois não retorna stdout.
21413131313 00
2
thread.is_alive pode levar a uma condição de corrida. Veja ostricher.com/2015/01/python-subprocess-with-timeout
ChaimKut
132

A resposta de jcollado pode ser simplificada usando o classe 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
fonte
11
+1 para solução portátil simples. Você não precisa lambda:t = Timer(timeout, proc.kill)
jfs
3
+1 Essa deve ser a resposta aceita, porque não exige que a maneira pela qual o processo é iniciado seja alterada.
Dave Branton
1
Por que requer o lambda? O método encadernado p.kill não poderia ser usado sem o lambda lá?
Danny Staple
//, você gostaria de incluir um exemplo do uso disso?
Nathan Basanese
1
@tuk timer.isAlive()antes timer.cancel()significa que terminou normalmente
Charles
83

Se você está no 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
fonte
3
Bem, estou interessado em uma solução multiplataforma que funcione pelo menos no win / linux / mac.
Sridhar Ratnakumar
1
Eu gosto dessa abordagem baseada em unix. Idealmente, combinaríamos isso com uma abordagem específica do Windows (usando CreateProcess e Jobs) .. mas, por enquanto, a solução abaixo é simples, fácil e funciona até o momento.
Sridhar Ratnakumar 29/07/2009
3
Eu adicionei uma solução portátil, veja a minha resposta
fly-by-wire
4
Esta solução funcionaria apenas se o sinal.signal (signal.SIGALARM, alarm_handler) for chamado a partir do encadeamento principal. Veja a documentação para signal
volatilevoid.
Infelizmente, ao executar (no linux) no contexto de um módulo Apache (como mod_python, mod_perl ou mod_php), descobri que o uso de sinais e alarmes não é permitido (presumivelmente porque eles interferem na lógica IPC do Apache). Portanto, para atingir o objetivo de esgotar o tempo limite de um comando, fui forçado a escrever "laços pai" que iniciam um processo filho e, em seguida, ficam em um ciclo de "suspensão" e observando o relógio (e possivelmente também monitorando a saída da criança).
Peter
44

Aqui está a solução de Alex Martelli como um módulo com a eliminação adequada do processo. As outras abordagens não funcionam porque não usam proc.communicate (). Portanto, se você tiver um processo que produz muita saída, ele preenche seu buffer de saída e bloqueia até você ler algo sobre ele.

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
fonte
3
Isso não funcionará no Windows, mais a ordem das funções é revertida.
Hamish Grubijan
3
Às vezes, isso resulta em exceção quando outro manipulador se registra no SIGALARM e interrompe o processo antes que este "mate", acrescentou a solução alternativa. BTW, ótima receita! Usei isso para iniciar 50.000 processos com bugs até agora sem congelar ou travar o wrapper de manipulação.
Yaroslav Bulatov
Como isso pode ser modificado para ser executado em um aplicativo Threaded? Eu estou tentando usá-lo de dentro de threads de trabalho e getValueError: signal only works in main thread
wim
@Yaroslav Bulatov Obrigado pela informação. Qual foi a solução alternativa que você adicionou para lidar com o problema mencionado?
jpswain
1
Acabei de adicionar o bloco "try; catch", está dentro do código. BTW, a longo prazo, isso resultou em problemas, porque você só pode definir um manipulador SIGALARM e outros processos podem redefini-lo. Uma solução para isso é dada aqui - stackoverflow.com/questions/6553423/…
Yaroslav Bulatov
18

Eu modifiquei a resposta do sussudio . Agora função retorna: ( returncode, stdout, stderr, timeout) - stdoute stderré decodificado para utf-8 corda

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
fonte
18

surpreendeu ninguém mencionou usando timeout

timeout 5 ping -c 3 somehost

Obviamente, isso não funciona em todos os casos de uso, mas se você estiver lidando com um script simples, é difícil de superar.

Também disponível como gtimeout no coreutils via homebrewpara usuários de mac.

Karsten
fonte
1
Quer dizer: proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). Existe timeoutcomando no Windows como o OP pede?
jfs
No Windows, pode-se usar aplicativos como o git bash, que permite utilitários do bash no Windows.
Kaushik Acharya
@KaushikAcharya mesmo se você usar o git bash, quando o python chamar subprocesso, ele será executado no Windows; portanto, esse desvio não funcionará.
Naman Chikara
16

timeoutagora é suportado por call()e communicate()no módulo subprocesso (a partir do Python3.3):

import subprocess

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

Isso chamará o comando e gerará a exceção

subprocess.TimeoutExpired

se o comando não terminar após 20 segundos.

Você pode manipular a exceção para continuar seu código, algo como:

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

Espero que isto ajude.

James
fonte
há uma resposta existente que menciona o timeoutparâmetro . Embora mencioná-lo mais uma vez não faria mal.
jfs
//, acho que o OP está procurando uma solução para o Python mais antigo.
Nathan Basanese
11

Outra opção é gravar em um arquivo temporário para evitar o bloqueio do stdout em vez de precisar pesquisar com o communic (). Isso funcionou para mim, onde as outras respostas não; por exemplo no 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()
Matt
fonte
Parece incompleto - o que é tempfile?
usar o seguinte comando
Inclua "import tempfile", "import time" e "shell = True" dentro da chamada "Popen" (cuidado com "shell = True")!
Eduardo Lucio
11

Eu não sei por que não é mencionado, mas desde o Python 3.5, há um novo subprocess.runcomando universal (que deve substituir check_call, check_output...) e que possui otimeout parâmetro.

subprocess.run (args, *, stdin = nenhum, entrada = nenhum, stdout = nenhum, stderr = nenhum, shell = falso, cwd = nenhum, tempo limite = nenhum, verifique = falso, codificação = nenhum, erros = nenhum)

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

Isso gera uma subprocess.TimeoutExpiredexceção quando o tempo limite termina.

Jean-François Fabre
fonte
6

Aqui está a minha solução, eu estava usando Thread e Evento:

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

Em ação:

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

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

A solução que eu uso é prefixar o comando shell com timelimit . Se o comando demorar muito, o timelimit o interromperá e o Popen terá um código de retorno definido por timelimit. Se for> 128, significa que o limite de tempo interrompeu o processo.

Consulte também subprocesso python com tempo limite e saída grande (> 64K)

bortzmeyer
fonte
Eu uso uma ferramenta semelhante chamada timeout- packages.ubuntu.com/search?keywords=timeout - mas nem funciona no Windows, funciona?
Sridhar Ratnakumar
5

Eu adicionei a solução com threading do jcolladomeu módulo Python easyprocess .

Instalar:

pip install easyprocess

Exemplo:

from easyprocess import Proc

# shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout
ponty
fonte
O módulo easyprocess ( code.activestate.com/pypm/easyprocess ) funcionou para mim, mesmo usando-o no multiprocessamento.
iChux
5

se você estiver usando python 2, tente

import subprocess32

try:
    output = subprocess32.check_output(command, shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
    print e
ThOong Ku
fonte
1
Provavelmente não está funcionando no Windows, como solicitado na pergunta inicial
Jean-Francois T.
5

Anexar o comando Linux timeoutnão é uma solução alternativa ruim e funcionou para mim.

cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
Vikram Hosakote
fonte
Como posso imprimir as strings de saída durante a execução do subprocesso? - As mensagens de saída são retornadas por subprocesso.
Ammad
3

Eu implementei o que pude reunir de alguns deles. Isso funciona no Windows e, como esse é um wiki da comunidade, acho que eu também compartilharia meu código:

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

Em seguida, de outra classe ou arquivo:

        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
fonte
Na verdade, isso provavelmente não funciona. A terminate()função marca um segmento como finalizado, mas na verdade não finaliza o segmento! Posso verificar isso no * nix, mas não tenho um computador Windows para testar.
dotancohen
2

Depois de entender o processo completo de máquinas em * unix, você encontrará facilmente uma solução mais simples:

Considere este exemplo simples de como tornar o método communic () com tempo limite, usando select.select () (disponível quase em todo lugar no * nix atualmente). Isso também pode ser escrito com epoll / poll / kqueue, mas a variante select.select () pode ser um bom exemplo para você. E as principais limitações do select.select () (speed e 1024 max fds) não são aplicáveis ​​à sua tarefa.

Isso funciona no * nix, não cria threads, não usa sinais, pode ser lançado a partir de qualquer thread (não apenas principal) e rápido o suficiente para ler 250mb / s de dados do stdout na minha máquina (i5 2.3ghz).

Há um problema ao ingressar no stdout / stderr no final da comunicação. Se você tiver uma saída enorme de programa, isso poderá levar ao grande uso de memória. Mas você pode ligar para communic () várias vezes com tempos limite menores.

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
fonte
2
Isso aborda apenas a metade do problema do Unix.
Spaceghost
2

Você pode fazer isso usando 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
fonte
1

Eu usei killableprocess com sucesso no Windows, Linux e Mac. Se você estiver usando o Cygwin Python, precisará da versão do killableprocess da OSAF porque, caso contrário, os processos nativos do Windows não serão mortos.

Heikki Toivonen
fonte
Parece que killableprocess não adiciona um tempo limite à chamada Popen.communicate ().
Wim Coenen
1

Embora eu não tenha examinado isso extensivamente, esse decorador que encontrei no ActiveState parece ser bastante útil para esse tipo de coisa. Junto com subprocess.Popen(..., close_fds=True), pelo menos estou pronto para scripts de shell em Python.

Ehtesh Choudhury
fonte
Este decorador usa signal.alarm, que não está disponível no Windows.
dbn
1

Essa solução mata a árvore do processo em caso de shell = True, passa parâmetros para o processo (ou não), tem um tempo limite e obtém a saída stdout, stderr e process da chamada de retorno (usa psutil para o kill_proc_tree). Isso foi baseado em várias soluções publicadas no SO, incluindo o jcollado's. Postagem em resposta aos comentários de Anson e jradice na resposta de jcollado. Testado no Windows Srvr 2012 e Ubuntu 14.04. Observe que, para o Ubuntu, você precisa alterar a chamada parent.children (...) para 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
fonte
1

Existe uma idéia para subclassificar a classe Popen e estendê-la com alguns decoradores simples de métodos. Vamos chamá-lo de 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 [email protected]', stdout=PIPE, timeout=1).communicate()
Alexander Yakovlev
fonte
1

Tive o problema de querer encerrar um subprocesso de multithreading se demorasse mais que um determinado tempo limite. Eu queria definir um tempo limite Popen(), mas não funcionou. Então, percebi que Popen().wait()é igual a, call()e tive a ideia de definir um tempo limite dentro do .wait(timeout=xxx)método, que finalmente funcionou. Assim, resolvi-o desta maneira:

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
fonte
0

Infelizmente, estou vinculado a políticas muito rigorosas sobre a divulgação do código fonte pelo meu empregador, portanto não posso fornecer o código real. Mas, para meu gosto, a melhor solução é criar uma subclasse substituindo Popen.wait()a pesquisa em vez de esperar indefinidamente e Popen.__init__aceitar um parâmetro de tempo limite. Depois de fazer isso, todos os outros Popenmétodos (que chamam wait) funcionarão conforme o esperado, incluindocommunicate .

Patrick Narkinsky
fonte
0

https://pypi.python.org/pypi/python-subprocess2 fornece extensões ao módulo de subprocessos, que permitem que você espere até um determinado período de tempo, caso contrário, será encerrado.

Portanto, espere até 10 segundos para o processo terminar, caso contrário, mate:

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

timeout =  10

results = pipe.waitOrTerminate(timeout)

Isso é compatível com Windows e Unix. "results" é um dicionário, contém "returnCode", que é o retorno do aplicativo (ou None, se ele tiver que ser morto), além de "actionTaken". que será "SUBPROCESS2_PROCESS_COMPLETED" se o processo for concluído normalmente ou uma máscara de "SUBPROCESS2_PROCESS_TERMINATED" e SUBPROCESS2_PROCESS_KILLED, dependendo da ação executada (consulte a documentação para obter detalhes completos)

Tim Savannah
fonte
0

para python 2.6+, use 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
fonte
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
fonte
-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
fonte
isso é uma abominação
Corey Goldberg
-2

Estava apenas tentando escrever algo mais simples.

#!/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
fonte
time.sleep (1) é uma péssima idéia - imagine que você queira executar muitos comandos que levariam cerca de 0,002seg. Você deve, antes, aguarde enquanto poll () (ver selecionar, por ePol Linux recomendado :)
ddzialak