Timeout Python Paramiko com longa execução, precisa de saída completa

Há muitos tópicos que tocam em parte do título, mas nada que satisfaça a coisa toda. Eu estou empurrando um comando em um servidor remoto e preciso da saída completa após um longo tempo de execução, digamos 5 minutos ou mais. Usando o canal, eu consegui definir um tempo limite, mas quando eu li de volta, obtive apenas uma pequena parte da saída. A solução parece ser esperar por channel.exit_status_ready (). Isso funcionou em uma chamada bem-sucedida, mas uma chamada com falha nunca acionaria o tempo limite do canal. Depois de revisar os documentos, eu acho que isso ocorre porque o tempo limite só funciona em uma operação de leitura e a espera pelo status de saída não se qualifica. Aqui está essa tentativa:

channel = ssh.get_transport().open_session()
channel.settimeout(timeout)
channel.exec_command(cmd)  # return on this is not reliable
while True:
    try:
        if channel.exit_status_ready():
            if channel.recv_ready():  # so use recv instead...
                output = channel.recv(1048576)
                break
        if channel.recv_stderr_ready():  # then check error
            error = channel.recv_stderr(1048576)
            break
    except socket.timeout:
        print("SSH channel timeout exceeded.")
        break
    except Exception:
        traceback.print_exc()
        break

Linda, não é? Desejo que funcionou.

Minha primeira tentativa de solução foi usar time.time () para começar, e então checar start - time.time ()> timeout. Isso parece simples, mas na minha versão atual, eu imprimo start - time.time () com um tempo limite fixo que deve disparar uma quebra ... e ver diferenças que duplicam e triplicam o tempo limite sem que ocorra a quebra. Para economizar espaço, mencionarei minha terceira tentativa, que eu anotei com essa. Eu li aqui sobre o uso de select.select para esperar pela saída, e notei na documentação que há um timeout lá também. Como você verá no código abaixo, eu misturei todos os três métodos - tempo limite do canal, time.time timeout e selecione timeout - e ainda assim tenho que matar o processo. Aqui está o frankencode:

channel = ssh.get_transport().open_session()
channel.settimeout(timeout)
channel.exec_command(cmd)  # return on this is not reliable
print("{0}".format(cmd))
start = time.time()
while True:
    try:
        rlist, wlist, elist = select([channel], [], [],
            float(timeout))
        print("{0}, {1}, {2}".format(rlist, wlist, elist))
        if rlist is not None and len(rlist) > 0:
            if channel.exit_status_ready():
                if channel.recv_ready():  # so use recv instead...
                    output = channel.recv(1048576)
                    break
        elif elist is not None and len(elist) > 0:
            if channel.recv_stderr_ready():  # then check error
                error = channel.recv_stderr(1048576)
                break
        print("{0} - {1} = {2}".format(
            time.time(), start, time.time() - start))
        if time.time() - start > timeout:
            break
    except socket.timeout:
        print("SSH channel timeout exceeded.")
        break
    except Exception:
        traceback.print_exc()
        break

Aqui está uma saída típica:

[<paramiko.Channel 3 (open) window=515488 -> <paramiko.Transport at 0x888414cL (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>], [], []
1352494558.42 - 1352494554.69 = 3.73274183273

A linha superior é [rlist, wlist, elist] de select, a linha inferior é time.time () - start = (time.time () - start). Eu tenho essa corrida para quebrar contando as iterações e quebrando na parte inferior da tentativa depois de looping 1000 vezes. o tempo limite foi definido como 3 na execução da amostra. O que prova que nós passamos pela tentativa, mas obviamente, nenhuma das três maneiras que deveriam estar dando certo funciona.

Sinta-se à vontade para entrar no código se eu, fundamentalmente, entendi mal alguma coisa. Eu gostaria que isso fosse uber-pythônico e ainda esteja aprendendo.

questionAnswers(3)

yourAnswerToTheQuestion