3

urllib2.urlopen/requests.postが永久にブロックされsocket.recv、二度と戻らないという問題が発生しています。

なぜこれが起こっているのかを調べてその問題に対処しようとしていますが、その間に、それが永久にブロックされるのを防ぐ方法があるのだろうかと思いました。

timeoutのオプションの引数についてはすでに知っていurllib2.urlopenますsocket.setdefaulttimeoutが、残念ながら、私のユースケースでは、POSTを使用してファイルをアップロードしているため、タイムアウトは解決策ではありません。使用するタイムアウト値は、通常のファイルアップロードを中断するリスクがあります。

シグナルを使用した解決策もいくつか見ましたが、これはタイムアウトを使用した場合と同じ問題が発生します(また、メインスレッドからこれを行っていないため、問題はありません)。

おそらく一定時間ソケットを介してデータが送受信されなかった場合にのみタイムアウトすることは可能ですか?または、select / pollを使用して、発生しているデッドロック/ブロッキングを防ぐ方法はありますか?

urllib2.urlopenselect / pollを使用した解決策がある場合、これを/に組み込むにはどうすればよいrequests.postですか?


また、書き込みタイプのインターフェイスを介してファイルデータを送信できる場合は、ファイルの反復処理とチャンクの送信を一度に制御できるので、ストールを回避するのに十分な制御ができると思いました。それを達成する方法がわからないので、私は質問をしました:file.writeインターフェースでファイルをアップロードします

更新 私は常にtimeoutPythonでの意味について誤解していたようですが、実際にはアイドルタイムアウトまたは読み取り/書き込みタイムアウトであるようです(おそらく初めてGuidoに同意しませんでした)。私はいつもそれが応答が戻るべき最大の時間だと思っていました-これを指摘してくれてありがとう@tomasz!!

しかし、タイムアウトパラメータを追加した後(との両方urllib2でテスト済みrequests)、タイムアウトが正しく機能しない、非常に奇妙で微妙なシナリオに遭遇しました。これは、バグであるとますます信じる傾向があります。私は問題が何であるかを正確に調査し、見つけることを続けるつもりです。もう一度、これを手伝ってくれたtomaszに感謝します!

4

3 に答える 3

6

OS レベルで TCP 設定を微調整することでハング状態を取り除くことができると思いますが、アプリケーションが専用の (そして保守可能な) マシンで動作しないと仮定すると、より一般的な解決策を探す必要があります。

あなたは尋ねました:

おそらく一定時間ソケットを介してデータが送受信されていない場合にのみタイムアウトすることは可能ですか?

そして、これはまさにsocket.settimeout(または に渡されたものurllib2) が与える振る舞いです。SIGALRM に基づくタイムアウト (遅いデータ転送中でも終了する) とは対照的に、ソケットに渡されるタイムアウトは、定義された期間中にデータが送信されなかった場合にのみ発生します。socket.sendorの呼び出しはsocket.recv、期間中にすべてのデータではなく一部のデータが送信された場合、部分的なカウントを返す必要がありurllib2、残りのデータを送信するために後続の呼び出しを使用します。

これを言うと、POST 呼び出しが複数のsend呼び出しで実行され、データを送信せずにブロックされてタイムアウトになる場合、アップロードの途中で POST 呼び出しが終了する可能性があります。アプリケーションによって適切に処理されないという印象を与えましたが、プロセスの強制終了または単に接続の切断に似ているため、適切に処理されるべきだと思います。

socket.settimeout問題が解決しないことをテストして確認しましたか? それとも、動作がどのように実装されているかわかりませんでしたか? 前者が正しい場合、もう少し詳細を教えてください。タイムアウトを設定するだけで安全だと確信しています。Pythonは、動作が上記のような低レベルのBSDソケット実装を使用しているだけだからです。さらに参考資料を提供するには、setsockoptman ページSO_RCVTIMEOSO_SNDTIMEOオプションを参照してください。socket.settimeoutこれらの機能とオプションを正確に使用することを期待しています。

--- 編集 --- (いくつかのテスト コードを提供するため)

そのため、モジュールを入手しRequestsて動作をテストすることができましたurllib2recvすべての呼び出しの間隔を広げてデータのブロックを受信して​​いたサーバーを実行しました。予想どおり、間隔が指定されたタイムアウトに達すると、クライアントはタイムアウトしました。例:

サーバ

import socket
import time

listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.bind(("localhost", 12346))
listener.listen(1)
sock,_ = listener.accept()

interval = 0.5
while 1:
  interval += 1 # increase interval by 1 second
  time.sleep(interval)
  # Get 1MB but will be really limited by the buffer
  data = sock.recv(1000000)
  print interval, len(data)
  if not data:
    break

クライアント (リクエストモジュール)

import requests

data = "x"*100000000 # 100MB beefy chunk
requests.post("http://localhost:12346", data=data, timeout=4)

クライアント (urllib2 モジュール)

import urllib2

data = "x"*100000000 # 100MB beefy chunk
urllib2.urlopen("http://localhost:12346", data=data, timeout=4)

出力 (サーバー)

> 1.5 522832
> 2.5 645816
> 3.5 646180
> 4.5 637832 <--- Here the client dies (4.5 seconds without data transfer)
> 5.5 294444
> 6.5 0

両方のクライアントで例外が発生しました:

# urllib2
URLError: timeout('timed out',)

# Requests
Timeout: TimeoutError("HTTPConnectionPool(host='localhost', port=12346): Request timed out. (timeout=4)",)

すべてが期待どおりに機能します!引数としてタイムアウトを渡さない場合urllib2も、 でうまく反応しましたがsocket.setdefaulttimeout、反応Requestsしませんでした。内部実装はデフォルト値をまったく使用する必要がなく、渡された引数に応じて単純に上書きしたり、非ブロッキング ソケットを使用したりできるため、驚くことではありません。

私は以下を使用してこれを実行しています:

OSX 10.8.3
Python 2.7.2
Requests 1.1.0
于 2013-03-23T22:07:51.617 に答える
1

あなたは、無期限のブロックが「非常にまれに」発生し、これが発生したときにファイルのアップロードが失敗しないようにするためのフォールバックを探していると述べています. この場合、投稿呼び出しにタイムアウトを使用し、タイムアウトの場合は投稿を再試行することをお勧めします。これに必要なのは単純な for ループだけで、タイムアウト以外の何かが発生した場合は中断します。

もちろん、これが発生した場合は警告メッセージをログに記録し、これが発生する頻度を監視する必要があります。そして、フリーズの根本的な原因を見つけようとする必要があります(あなたが意図していると述べたように)。

于 2013-03-25T09:11:05.337 に答える
0

考えられる決定の1つ-urllib2要求をALRM信号処理を使用してブロックにネストするか、タイムアウト時に強制的に停止してスレッドに入れることができます。これにより、内部urllib2の問題にもかかわらず、タイムアウトによってリクエストが強制的に停止されます。この場合の古い質問: Python:タイムアウト時にサブプロセスを強制終了または終了します

于 2013-03-18T17:19:19.603 に答える