5

タイトルの一部に触れるトピックはたくさんありますが、全体を完全に満足させるものはありません。リモート サーバーでコマンドをプッシュしていて、長い実行時間 (たとえば 5 分程度) の後に完全な出力が必要です。チャネルを使用してタイムアウトを設定できましたが、stdout を読み返すと、出力のごく一部しか得られませんでした。解決策は、channel.exit_status_ready() を待つことのようです。これは成功した呼び出しでは機能しましたが、失敗した呼び出しによってチャネル タイムアウトがトリガーされることはありませんでした。ドキュメントを確認したところ、タイムアウトは読み取り操作でのみ機能し、終了ステータスを待つことは資格がないためだと理論付けています。その試みは次のとおりです。

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

かわいいですね。それがうまくいったことを願っています。

私の最初の解決策の試みは、time.time() を使用して開始し、次に start - time.time() > timeout を確認することでした。これは簡単に思えますが、私の現在のバージョンでは、ブレークをトリガーする固定タイムアウトで start - time.time() を出力し、ブレークが発生しないでタイムアウトを 2 倍および 3 倍にする違いを確認します。スペースを節約するために、3 回目の試みについて言及します。ここで select.select を使用して出力を待機する方法について読み、ドキュメントにタイムアウトがあることにも注意しました。以下のコードからわかるように、チャネル タイムアウト、time.time タイムアウト、および選択タイムアウトの 3 つの方法をすべて組み合わせましたが、それでもプロセスを強制終了する必要があります。フランケンコードは次のとおりです。

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

典型的な出力は次のとおりです。

[<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

一番上の行は select の [rlist, wlist, elist] で、一番下の行は time.time() - start = (time.time() - start) です。繰り返しを数え、1000回ループした後、試行の最後で中断することで、この実行を中断させました。サンプルの実行では、タイムアウトは 3 に設定されました。これは、試行をやり遂げたことを証明していますが、明らかに、タイムアウトになるはずの 3 つの方法のいずれも機能しません。

基本的に何かを誤解している場合は、遠慮なくコードを読み込んでください。これを uber-Pythonic にしたいのですが、まだ学習中です。

4

3 に答える 3

3

私はまだテストの最中ですが、これが役立つかもしれません。Pythonのキャッチオールタイムアウトを含むさまざまなタイプのタイムアウトに苦労し、実際の問題はサーバーがプロセスを終了することを信頼できないことであることに気付いた後、私はこれを行いました:

chan = ssh.get_transport().open_session()

cmd = "timeout {0} {1}\n".format(timeouttime, cmd)

chan.exec_command(cmd)

サーバーは、希望どおりにすぐに終了しないtimeouttime場合にタイムアウトし、終了したコマンドによってチャネルが強制終了されます。cmd唯一の問題は、GNU coreutils がサーバーに存在する必要があることです。代替手段があることに失敗する。

于 2012-12-26T21:49:22.833 に答える
2

私は同じ種類の問題を抱えています。シグナリングで対処できると思います。http://docs.python.org/2/library/signal.html

これがどのように機能するかを示す単純な例です。

import signal, time                          

def handler(signum, frame):                  
    pass                                     

# Set the signal handler and a 2-second alarm
signal.signal(signal.SIGALRM, handler)       
signal.alarm(2)                              

# This is where your operation that might hang goes
time.sleep(10)                               

# Disable the alarm                          
signal.alarm(0)                              

ここでは、アラームを 2 秒に設定しています。Time.sleep は 10 秒で呼び出されます。もちろん、スリープが終了する前にアラームがトリガーされます。time.sleep の後に何らかの出力を配置すると、プログラムの実行がそこで再開されることがわかります。

コントロールを別の場所で続行する場合は、ぶら下がっている呼び出しを try/except でラップし、ハンドラー関数で例外を発生させます。

うまくいくと確信していますが、paramiko 呼び出しでまだテストしていません。

于 2012-11-27T15:22:56.460 に答える