2013-03-18 70 views
3

我遇到了一個問題,urllib2.urlopen/requests.post是非常偶然永遠阻止socket.recv永遠不會返回。如何防止永久阻止urlopen

我想知道爲什麼會發生這種情況並解決該問題,但同時我想知道是否有防止永久阻止的方法?

我已經知道了urllib2.urlopensocket.setdefaulttimeouttimeout可選參數,但不幸的是我的使用情況下超時是沒辦法,因爲我上載的文件與POST我用將有可能中斷正常的文件上傳任何超時值。我也看到了一些使用信號的解決方案,但是這會和我使用超時的問題相同(也是因爲我沒有從主線程執行此操作而出問題)。

只有在沒有數據通過套接字發送/接收一定時間的情況下才有可能超時?或者也許有一些方法可以使用select/poll來防止我遇到的死鎖/阻塞?

如果有解決方案使用選擇/民意調查,我會怎麼去把它納入urllib2.urlopen/requests.post


我也有這個想法,如果我可以通過接口的寫入類型發送文件數據,所以我會控制遍歷文件,並在同一時間發送塊我大概可以有足夠的控制,以避免攤位。我不知道如何實現它,雖然如此,我問了一個問題:Upload a file with a file.write interface

UPDATE 好像我一直的在python timeout含義的誤解,現在看來,這實際上是一個空閒超時或讀/寫超時(可能是我第一次disagreed with Guido)。我一直認爲這是響應應該返回的最長時間 - 謝謝@tomasz指出這一點!

但添加超時參數(同時用urllib2requests測試)後,我遇到了一些非常奇怪和微妙的情況,可能是特定於mac,超時不能正常工作,我越來越傾向於相信是一個錯誤。我將繼續調查並找出問題所在。再次感謝你對此的幫助!

+0

首先 - *爲什麼*它永遠阻擋? – 2013-03-18 16:58:46

+0

@CodePainters我不知道 - 理想情況下,我會解決實際問題,我會繼續嘗試,但它可能是一個服務器端問題(我不控制),直到我找到原因, d喜歡設置一些作爲後備的東西,以便上傳永遠不會凍結,並在此期間作爲修補程序發佈。 – GP89 2013-03-18 17:02:38

回答

5

我相信你可以通過在操作系統級別調整您的TCP設置擺脫掛狀態,但假設您的應用程序不會在專用(並由您維護)機器上工作,您應該尋求更通用的解決方案。

你問:

是否有可能只在沒有數據發送超時通過插座/收到了一定的時間也許

而這正是行爲socket.settimeout(或傳遞給urllib2的那個)會給你。與基於SIGALRM的超時(即使在數據傳輸緩慢時會終止)相反,只有在定義的時間段內沒有數據傳輸時,傳遞到套接字的超時纔會發生。如果socket.sendsocket.recv的呼叫應該返回部分計數,如果在此期間某些(但不是全部)數據已傳輸,並且urllib2然後將使用後續呼叫來傳輸剩餘數據。

說了這樣的話,如果POST調用將在多個send調用中執行,並且任何(但不是第一個)調用都會在不發送任何數據的情況下阻塞並超時,那麼您的POST調用仍可能在上傳中途的某個地方終止。您給人的印象是它不會被您的應用程序正確處理,但我認爲它應該,因爲它類似於強制終止該流程或者簡單地將連接斷開。

您是否測試過並確認socket.settimeout不能解決您的問題?或者你只是不確定行爲是如何實施的?如果前者是正確的,請你提供更多的細節?我相當肯定你只需設置超時時間是安全的,因爲python只是使用低級BSD套接字實現,其行爲如上所述。爲了給您更多的參考,請看setsockopt手冊頁和SO_RCVTIMEOSO_SNDTIMEO選項。我期望socket.settimeout恰好使用這些功能和選項。

---編輯---(提供一些測試代碼)

所以我能夠得到Requests模塊和測試與urllib2沿着行爲。我已經運行了服務器,它在每個recv調用之間增加間隔接收數據塊。如預期的那樣,當間隔達到指定的超時時間時,客戶端超時。例如:

服務器

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

看起來像一個巨大的一巴掌額頭時刻..從一個非常低的值測試'setdefaulttimeout'我可以看到它對上傳沒有影響(這是上傳罰款)。出於某種原因,我認爲它會在一段時間之後從一開始就超時。我猜是因爲我幾乎總是在過去完成Web請求,並且從通話開始到請求結束之間的時間很短,看起來超時是對整個時間的限制操作而不是讀/寫超時。謝謝你指出! – GP89 2013-03-25 10:50:59

+0

另外,你知道它會提高errno嗎? 'errno.ETIMEDOUT'我猜想,但是查找'SO_RCVTIMEO'和'SO_SNDTIMEO',它看起來可能有些不同。 – GP89 2013-03-25 10:51:21

+0

我一直在用'requests.put'測試它,並且設置'socket.setdefaulttimeout'似乎不工作,並且傳遞'timeout' kwarg意味着我無法上傳任何東西 - 我只是不斷地收到一個套接字錯誤和「資源暫時不可用」。任何想法? – GP89 2013-03-25 13:19:48

0

其中一個可能的決定 - 您可以將您的urllib2請求嵌套到具有ALRM信號處理的塊中,或者將其放入超時時強制停止的線程。 這將強制停止您的要求通過超時,儘管任何內部urllib2的問題,有關這個案子的老問題: Python: kill or terminate subprocess when timeout

+0

但是這不是OP所需要的:「只有在沒有數據通過套接字發送/接收一段時間的情況下才有可能超時? – 2013-03-18 17:22:12

+0

是的,我不能使用信號,因爲我沒有從主線程上傳,我認爲它會工作,就像指定一個超時無論如何(這不會對我工作)。而使用線程的想法將與指定超時的效果相同。 – GP89 2013-03-18 17:28:12

+0

老問題:http://stackoverflow.com/questions/5686490/detect-socket-hangup-without-sending-or-receiving在你的情況下似乎很有用 – moonsly 2013-03-18 18:22:01

1

你提到無限期阻塞情況「很偶然」,而你正在尋找一個備用,以避免失敗的文件上傳時出現這種情況。在這種情況下,我建議使用超時時間來發送郵件,並在超時情況下重試郵件。所有這一切都需要一個簡單的for循環,如果發生超時以外的任何事情,就會中斷。

當然,您應該在發生這種情況時記錄一條警告消息,並監視發生這種情況的頻率。你應該嘗試找出凍結的根本原因(正如你提到的你打算的那樣)。

+0

看起來像我可以使用'超時',你是對的。我一直認爲超時是通話所需的最長時間,我認爲我不能準確地進行鍛鍊(如果超過幾GB的上傳時間,並且用戶不得不啓動),但似乎我對超時的理解是錯誤的,它確實充當了我正在尋找的讀/寫超時! – GP89 2013-03-25 10:59:56

相關問題