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