[Linux] curl 7.19.7 陷入無窮迴圈的問題

[Linux] curl 7.19.7 陷入無窮迴圈的問題

最近客戶回報了一個問題,上去看了一下,

curl 這隻程式似乎是卡住不動了,CPU 使用率飆很高…

用幾個工具來觀察看看:

 

1. 用 strace 看看 curl 現在在作什麼

執行 strace -f -p <pid> 看看 curl 是不是在等什麼東西…

嗯… 什麼都沒印出來… 看來它並不是呼叫到類似 wait/sleep 之類的函式在等…

 

2. 用 gdb 看看 curl 現在在作什麼

執行 gdb –pid <pid> 進去執行 bt 看看目前的 call stack,

奇怪的是每次退出後再執行一次 gdb,call stack 就又不一樣,

像是一開始可能像這樣:

#0  addbyter (output=111, data=0x7fffa7faf060) at mprintf.c:1023
1023	    infop->buffer++; /* increase pointer */
#0  addbyter (output=111, data=0x7fffa7faf060) at mprintf.c:1023
#1  0x00007f929bcbe11e in dprintf_formatf (data=0xc29d20, stream=0xffffffffffffffec, format=0x1 <Address 0x1 out of bounds>, ap_save=<value optimized out>) at mprintf.c:885
#2  0x00007f929bcbe835 in curl_mvsnprintf (buffer=<value optimized out>, maxlength=<value optimized out>, format=<value optimized out>, ap_save=<value optimized out>) at mprintf.c:1040
#3  0x00007f929bcbd233 in curl_msnprintf (buffer=<value optimized out>, maxlength=<value optimized out>, format=<value optimized out>) at mprintf.c:1057
#4  0x00007f929bcaf5e6 in Curl_failf (data=0xc29560, fmt=<value optimized out>) at sendf.c:176
#5  0x00007f929bcc7161 in Curl_connecthost (conn=0xc34e10, remotehost=0xc35830, sockconn=0xc34f50, addr=0x7fffa7faf308, connected=0x7fffa7faf31f) at connect.c:902
#6  0x00007f929bcbbac6 in ConnectPlease (data=0xc29560, in_connect=0x7fffa7faf3a0, asyncp=<value optimized out>, protocol_done=0x7fffa7faf3ae) at url.c:2944
#7  setup_conn (data=0xc29560, in_connect=0x7fffa7faf3a0, asyncp=<value optimized out>, protocol_done=0x7fffa7faf3ae) at url.c:4740
#8  Curl_connect (data=0xc29560, in_connect=0x7fffa7faf3a0, asyncp=<value optimized out>, protocol_done=0x7fffa7faf3ae) at url.c:4819
#9  0x00007f929bcc3aa0 in connect_host (data=0xc29560) at transfer.c:2488
#10 Curl_perform (data=0xc29560) at transfer.c:2626
#11 0x00000000004086ef in operate (argc=0, argv=<value optimized out>) at main.c:5002
#12 main (argc=0, argv=<value optimized out>) at main.c:5317

 

第二次執行 gdb 可能又變下面這樣:

#0  dprintf_formatf (data=0x7fffa7da11f0, stream=0x7f77d782a050 <addbyter>, format=0x7f77d784b3dd "Connection time-out", ap_save=<value optimized out>) at mprintf.c:680
680	      } while(*++f && ('%' != *f));
#0  dprintf_formatf (data=0x7fffa7da11f0, stream=0x7f77d782a050 <addbyter>, format=0x7f77d784b3dd "Connection time-out", ap_save=<value optimized out>) at mprintf.c:680
#1  0x00007f77d782b835 in curl_mvsnprintf (buffer=<value optimized out>, maxlength=<value optimized out>, format=<value optimized out>, ap_save=<value optimized out>) at mprintf.c:1040
#2  0x00007f77d781c55f in Curl_failf (data=0x17fc560, fmt=<value optimized out>) at sendf.c:173
#3  0x00007f77d7834161 in Curl_connecthost (conn=0x1807e10, remotehost=0x1808830, sockconn=0x1807f50, addr=0x7fffa7da13b8, connected=0x7fffa7da13cf) at connect.c:902
#4  0x00007f77d7828ac6 in ConnectPlease (data=0x17fc560, in_connect=0x7fffa7da1450, asyncp=<value optimized out>, protocol_done=0x7fffa7da145e) at url.c:2944
#5  setup_conn (data=0x17fc560, in_connect=0x7fffa7da1450, asyncp=<value optimized out>, protocol_done=0x7fffa7da145e) at url.c:4740
#6  Curl_connect (data=0x17fc560, in_connect=0x7fffa7da1450, asyncp=<value optimized out>, protocol_done=0x7fffa7da145e) at url.c:4819
#7  0x00007f77d7830aa0 in connect_host (data=0x17fc560) at transfer.c:2488
#8  Curl_perform (data=0x17fc560) at transfer.c:2626
#9  0x00000000004086ef in operate (argc=0, argv=<value optimized out>) at main.c:5002
#10 main (argc=0, argv=<value optimized out>) at main.c:5317

 

一開始百思不得其解,以為是 gdb 出了什麼問題…

後來參照 curl 的原始碼來看,總算有點理解了~

看來 curl 應該是在執行一個很大的迴圈,這迴圈因為某些原因出不來,成為無窮迴圈。

而我每次 gdb attach 上去時,call stack 就會停留在 attach 時的狀態,

所以 call stack 看起來會不一樣,但都是在同一段迴圈範圍內~~

 

最後找出來的問題應該是在 setup_conn() 這個函式裡面~

下面是這個函式的部分節錄 (簡化了一部分): 

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static CURLcode setup_conn()
{
conn->bits.proxy_connect_closed = FALSE;
for(;;)
{
if (CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET])
{
bool connected = FALSE;
result = ConnectPlease(data, conn, hostaddr, &connected);
if (connected)
{
result = Curl_protocol_connect(conn, protocol_done);
if(CURLE_OK == result)
conn->bits.tcpconnect = TRUE;
}
else
conn->bits.tcpconnect = FALSE;
if (conn->bits.proxy_connect_closed)
{
if(data->set.errorbuffer)
data->set.errorbuffer[0] = '\0';
data->state.errorbuf = FALSE;
continue;
}
}
}
}

 

稍微瞄一下這個函式就會覺得注意到 for (;;) 這個無窮迴圈的寫法,

如果沒有有效的 break/return 的話就穩死的~

照著流程想了一下,有可能出問題的情境可能有不只一種,像下面這是一種:

  1. 一開始 proxy_connect_closed = FALSE

  2. 第一次的 ConnectPlease() 呼叫成功,因此 connected = TRUE

  3. 接下來呼叫 Curl_protocol_connect(),這個函式接下來會呼叫 Curl_http_connect().

      3.1 Curl_http_connect() 會呼叫 Curl_proxyCONNECT().

      3.2 Curl_proxyCONNECT() 在遇到某些狀況時,會將 proxy_connect_closed 設成 TRUE (例如沒有收到回應資料、或是伺服器回了一個不是 200 OK 的值等等)

  4. 因為 proxy_connect_closed 的值是 TRUE, 迴圈裡的 continue 會跳到 for (;;) 的開頭繼續執行

  5. 假設之後的 ConnectPlease() 呼叫成功,因此 connected = TRUE

  6. 接下來呼叫 Curl_protocol_connect() 時,這個函式在看到之前的連線已經存在的狀況下,都會立刻結束,導致 proxy_connect_closed 沒有改變,還是 TRUE。

  7. 接下來的步驟就會一直在步驟 4~7 中間循環導致無窮迴圈。

  

幸運的是,上面這段程式在 curl 7.34.0 就已經被改善,無窮迴圈也拿掉了,

所以理論上客戶遇到的問題,只要更新 curl 到最新版 (現在是 7.39.0) 應該就能解決囉~

花了一整天在查這個問題,真的是好累呀… Orz

 

//
//

(本頁面已被瀏覽過 549 次)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料