0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Too many open files錯(cuò)誤導(dǎo)致服務(wù)器死循環(huán)

Linux閱碼場(chǎng) ? 來(lái)源:Linux閱碼場(chǎng) ? 2023-05-23 09:09 ? 次閱讀

0x01 前言

在服務(wù)器編程中,經(jīng)常會(huì)遇到 Too many open files 這個(gè)報(bào)錯(cuò),而且這個(gè)報(bào)錯(cuò)如果處理不好,很有可能會(huì)導(dǎo)致服務(wù)器死循環(huán)。

0x02 示例代碼

6eb5ae6a-f8fd-11ed-90ce-dac502259ad0.png

以上是我用rust寫(xiě)的一個(gè)非常簡(jiǎn)單的tcp服務(wù)器,它的主要邏輯是,先創(chuàng)建一個(gè)listener,然后再在循環(huán)里不斷調(diào)用listener.accept接收tcp連接,如果接收成功,就調(diào)用handle_client處理這個(gè)連接,如果接收失敗,就打印一行錯(cuò)誤日志。

handle_client里的邏輯也非常簡(jiǎn)單,就是等待客戶(hù)端關(guān)閉連接,或等待其發(fā)送任意數(shù)據(jù),當(dāng)這兩種情況發(fā)生時(shí),handle_client就會(huì)直接關(guān)閉這個(gè)連接。

當(dāng)然,如果在等待期間報(bào)錯(cuò)了,handle_client也會(huì)打印一行錯(cuò)誤日志。

下面我們就會(huì)使用這段程序,來(lái)演示服務(wù)器死循環(huán)的情況,這段程序不必非要用rust編寫(xiě),用其他語(yǔ)言也都可以。

測(cè)試代碼我已經(jīng)放到github了,如果想要自己動(dòng)手測(cè)試的,可以clone下來(lái)自己試下。

代碼地址:https://github.com/ytcoode/too-many-open-files

0x03 動(dòng)手演示

先啟動(dòng)該服務(wù)器:

6edcb6b8-f8fd-11ed-90ce-dac502259ad0.png

由上圖可見(jiàn),該服務(wù)器的進(jìn)程id是312004,監(jiān)聽(tīng)地址是0.0.0.0:9999。

再查看下該服務(wù)器已打開(kāi)的文件數(shù):

6f0a1270-f8fd-11ed-90ce-dac502259ad0.png

一共是10個(gè),主要包括標(biāo)準(zhǔn)輸入輸出、epoll、及一些socket。

再查看下該服務(wù)器進(jìn)程最多可打開(kāi)的文件數(shù):

6f3a0c50-f8fd-11ed-90ce-dac502259ad0.png

看選中行,Soft Limit那一列,其表示該進(jìn)程最多可用的文件描述符數(shù)量為1024個(gè),即最多可同時(shí)打開(kāi)的文件數(shù)為1024個(gè)。

我們把它改小一點(diǎn),方便后續(xù)測(cè)試:

6f53f6c4-f8fd-11ed-90ce-dac502259ad0.png

上圖中,先使用prlimit命令將該服務(wù)器進(jìn)程的Max open files數(shù)改成12,然后再用cat命令確認(rèn)下該改動(dòng)已生效。

至此,我們已經(jīng)設(shè)置好該服務(wù)器進(jìn)程最多可用的文件描述符數(shù)量為12,其當(dāng)前已用的文件描述符數(shù)量為10,所以該服務(wù)器最多還可以再接收2個(gè)tcp連接。

我們用 `ncat localhost 9999` 命令建立連接試一下,當(dāng)然你也可以用telnet, nc等其他命令,只要能建立tcp連接就行:

6f82eede-f8fd-11ed-90ce-dac502259ad0.png

由上圖服務(wù)器日志可見(jiàn),該tcp連接已建立成功。

再看下當(dāng)前服務(wù)器已使用的文件描述符數(shù)量:

6fc1c30c-f8fd-11ed-90ce-dac502259ad0.png

由上圖可見(jiàn),新建socket使用的文件描述符為10,當(dāng)前服務(wù)器進(jìn)程已使用11個(gè)文件描述符,到目前為止一切正常。

用同樣的命令再建立一個(gè)tcp連接,這次應(yīng)該也能連接成功,不過(guò)會(huì)有一些有意思的事情發(fā)生:

6ff862fe-f8fd-11ed-90ce-dac502259ad0.png

首先看上圖中最后一行info日志,它表示第二次tcp連接也建立成功了,如果此時(shí)去看文件描述符數(shù)量,也正好是12。

不過(guò)此次連接建立也導(dǎo)致不斷的error日志輸出,該服務(wù)器死循環(huán)了。

但此時(shí),如果我們關(guān)閉第二次ncat命令建立的tcp連接,服務(wù)器又不會(huì)一直輸出error日志了,它又會(huì)恢復(fù)到正常狀態(tài):

707b880a-f8fd-11ed-90ce-dac502259ad0.png

看上圖中的最后一條info日志,它表示第二個(gè)tcp連接正常關(guān)閉了,且當(dāng)前已建立的連接數(shù)量是1。

此時(shí),如果我們?nèi)タ次募枋龇麛?shù)量,其也變成了11,這里就不再截圖了,有興趣的可以自己動(dòng)手試下。

0x04 為什么會(huì)出現(xiàn)死循環(huán)?

首先,在linux的世界里,一切皆文件,這里就包括socket。

其次,linux為保證系統(tǒng)的整體性安全,為每個(gè)進(jìn)程限制了其最大可使用的文件描述符數(shù)量,即最大可打開(kāi)的文件數(shù),這個(gè)數(shù)量就是上面我們用 `cat /proc/$(pidof too-many-open-files)/limits` 命令輸出的Max open files行,Soft Limit列對(duì)應(yīng)的值,該值是可以通過(guò)各種方式修改的,在我的系統(tǒng)上,該值默認(rèn)為1024。

接著,我們啟動(dòng)了服務(wù)器,然后通過(guò) `l /proc/$(pidof too-many-open-files)/fd/` 命令查看該服務(wù)器已使用的文件描述符數(shù)量,其為10。

之后,我們用prlimit命令將該服務(wù)器進(jìn)程最大可使用的文件描述符數(shù)量改成了12,這樣該服務(wù)器就還只剩兩個(gè)文件描述符可用。

再之后,我們用ncat命令建立了兩個(gè)tcp連接,在服務(wù)器端的循環(huán)里,accept接收到這兩個(gè)連接并進(jìn)行處理,此時(shí)該服務(wù)器進(jìn)程消耗完了最后兩個(gè)可用的文件描述符。

接下來(lái),服務(wù)器代碼進(jìn)入下一次循環(huán),繼續(xù)調(diào)用accept嘗試接收新的連接,問(wèn)題的關(guān)鍵點(diǎn)也就出現(xiàn)在了這里。

accept是個(gè)系統(tǒng)調(diào)用,我們看下其對(duì)應(yīng)的內(nèi)核實(shí)現(xiàn):

70baa9a4-f8fd-11ed-90ce-dac502259ad0.png

這個(gè)是accept系統(tǒng)調(diào)用的入口函數(shù),沿著函數(shù)調(diào)用,可找到以下代碼:

70df7248-f8fd-11ed-90ce-dac502259ad0.png

由上圖可見(jiàn),在真正的do_accept之前,會(huì)先調(diào)用get_unused_fd_flags找一個(gè)還未被使用的文件描述符,如果尋找時(shí)報(bào)錯(cuò)了,即newfd < 0,則直接返回該錯(cuò)誤碼給用戶(hù)層,如果找到了一個(gè)可用的文件描述符,則開(kāi)始執(zhí)行真正的accept操作。

繼續(xù)看get_unused_fd_flags函數(shù):

712824f2-f8fd-11ed-90ce-dac502259ad0.png

它在調(diào)用其他函數(shù)之前,會(huì)通過(guò) rlimit(RLIMIT_NOFILE) 獲取當(dāng)前進(jìn)程最大可使用的文件描述符數(shù)量,即我們上面通過(guò)prlitmit命令設(shè)置的12。

繼續(xù)往下看,我們會(huì)找到以下代碼:

7147c500-f8fd-11ed-90ce-dac502259ad0.png

該函數(shù)的目的是分配一個(gè)文件描述符,即fd,圖中選中行之前是找到一個(gè)還未被使用的fd,然后判斷該 fd 是否 >= end,如果是,則goto到out,進(jìn)而return error,而這個(gè)error就是EMFILE。

那end值是什么呢?它就是上面用 rlimit(RLIMIT_NOFILE) 獲取的當(dāng)前進(jìn)程最大可用的文件描述符數(shù)。

結(jié)合上面的例子我們知道,當(dāng)服務(wù)器接收完兩個(gè)tcp連接后,其最大可使用的12個(gè)文件描述符已全部被用完,當(dāng)其循環(huán)到下一次accept系統(tǒng)調(diào)用后,會(huì)最終進(jìn)入到上圖這個(gè)函數(shù),這次新分配的fd值一定是12(因?yàn)閒d值從0開(kāi)始的,所以fd值為12表示第13個(gè)文件描述符),而我們又限制了該進(jìn)程最大可用12個(gè)文件描述符,即我們限制了end值為12,所以在上圖選中行進(jìn)行判斷時(shí),fd 一定是 >= end 的,所以,該函數(shù)一定會(huì)返回EMFILE這個(gè)錯(cuò)誤碼。

而EMFILE是什么呢?

7191e536-f8fd-11ed-90ce-dac502259ad0.png

它就是我們?cè)谶\(yùn)行測(cè)試程序時(shí)看到的 Too many open files 這個(gè)錯(cuò)誤。

示例程序調(diào)用accept收到這個(gè)錯(cuò)誤碼后,會(huì)打印一行error日志,然后繼續(xù)循環(huán)調(diào)用accept,然后繼續(xù)報(bào)錯(cuò),就這樣,服務(wù)器就在accept這里發(fā)生了死循環(huán)。

0x05 這個(gè)問(wèn)題如何處理?

因?yàn)?too many open files 是個(gè)臨時(shí)性錯(cuò)誤,當(dāng)進(jìn)程中的其他地方關(guān)閉了一些文件,或者管理人員調(diào)高了該進(jìn)程的 max open files值,accept就不會(huì)再報(bào) EMFILE 錯(cuò)誤,也就不會(huì)再死循環(huán)了。

所以其處理方法也很簡(jiǎn)單,就是在accept發(fā)生錯(cuò)誤時(shí),sleep一段時(shí)間,這樣既防止了cpu 100%的發(fā)生,也給進(jìn)程時(shí)間來(lái)調(diào)整已用及最大的文件描述符數(shù)。

0x06 用epoll也會(huì)有這個(gè)問(wèn)題嗎?

會(huì)有,epoll只是個(gè)通知機(jī)制,當(dāng)epoll檢測(cè)到有連接可被接收時(shí),還是會(huì)通過(guò)accept來(lái)接收這個(gè)連接。

不過(guò)這里分成兩種情況。

當(dāng)使用epoll的edge-triggered模式時(shí),正確寫(xiě)法是要一直循環(huán)調(diào)用accept接收連接,直到其返回 EAGAIN 或 EWOULDBLOCK 錯(cuò)誤碼,表示已經(jīng)沒(méi)有連接可接收了,這時(shí)才能退出accept循環(huán),但如果在這之前accept返回了 too many open files 這個(gè)錯(cuò)誤,就會(huì)發(fā)生死循環(huán)了。

當(dāng)使用epoll的level-triggered模式時(shí),可以不必一直循環(huán)調(diào)用accept直到其返回EAGAIN 或 EWOULDBLOCK,可以提前退出,但如果操作系統(tǒng)里還有建立好的連接等待被接收,epoll還是會(huì)一直通知應(yīng)用層,告知其要調(diào)用accept接收這些連接,如果此時(shí)文件描述符沒(méi)有了,accept還是會(huì)一直報(bào) too many open files 錯(cuò)誤,最終還是進(jìn)入到了死循環(huán)。

0x07 Go是如何處理的?

下面我們看下go內(nèi)置的http服務(wù)器,是如何處理這個(gè)問(wèn)題的:

71c64876-f8fd-11ed-90ce-dac502259ad0.png

當(dāng)accept返回err后,其會(huì)通過(guò)ne.Temporary()來(lái)檢查該err是否是臨時(shí)性錯(cuò)誤,如果是,則會(huì)根據(jù)一定的規(guī)則,sleep一段時(shí)間。

這里,臨時(shí)性錯(cuò)誤就包括 EMFILE,即too many open files錯(cuò)誤:

7203ec08-f8fd-11ed-90ce-dac502259ad0.png

我們也可以寫(xiě)個(gè)簡(jiǎn)單的例子測(cè)試下:

721b8264-f8fd-11ed-90ce-dac502259ad0.png

按照之前的方式,讓其觸發(fā) too many open files 這個(gè)錯(cuò)誤:

7244e2d0-f8fd-11ed-90ce-dac502259ad0.png

由圖可見(jiàn),和我們上面分析的一樣,其也陷入了死循環(huán),但是它用sleep的方式,防止cpu使用率100%。

0x08 Redis是如何處理的?

下面我們看下redis是如何處理這個(gè)問(wèn)題的:

727fddd6-f8fd-11ed-90ce-dac502259ad0.png

當(dāng)anetTcpAccept返回 too many open files 錯(cuò)誤時(shí),它只打印了一行錯(cuò)誤日志,就直接return了。

不過(guò)因?yàn)閞edis使用的是level-triggered模式的epoll,所以雖然這里直接return了,但因?yàn)榈讓拥倪B接沒(méi)接收出來(lái),epoll一直會(huì)調(diào)用這個(gè)函數(shù),然后一直報(bào)錯(cuò),進(jìn)而死循環(huán)。

實(shí)驗(yàn)下:

72b90a48-f8fd-11ed-90ce-dac502259ad0.png

可以看到,其一直在輸出這個(gè)錯(cuò)誤。

0x09 結(jié)語(yǔ)

希望通過(guò)這篇文章,能給大家的技術(shù)水平帶來(lái)一點(diǎn)提高。

審核編輯:彭靜
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    12

    文章

    8963

    瀏覽量

    85087
  • 程序
    +關(guān)注

    關(guān)注

    116

    文章

    3762

    瀏覽量

    80757
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4723

    瀏覽量

    68236
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    獨(dú)立看門(mén)狗檢測(cè)及如何解決軟件錯(cuò)誤導(dǎo)致故障

    其用于檢測(cè)和解決由軟件錯(cuò)誤導(dǎo)致的故障,當(dāng)計(jì)數(shù)達(dá)到設(shè)定的超時(shí)時(shí)間值時(shí)會(huì)產(chǎn)生系統(tǒng)復(fù)位。
    的頭像 發(fā)表于 01-23 09:14 ?1.2w次閱讀
    獨(dú)立看門(mén)狗檢測(cè)及如何解決軟件<b class='flag-5'>錯(cuò)誤導(dǎo)致</b>故障

    編譯錯(cuò)誤too many global/static 'bit' variables

    用的CodeVisionAVR編譯,編譯出錯(cuò),很多都是說(shuō)too many global/static 'bit' variablesError: D:\AVRproject\2014\test
    發(fā)表于 04-04 22:37

    too many jtag devices in chain

    用keil下載程序時(shí),出現(xiàn)too many jtag devices in chain,哪位大神知道怎么解決,十分感謝!
    發(fā)表于 01-20 13:46

    什么是服務(wù)器500錯(cuò)誤?

    500報(bào)錯(cuò):被稱(chēng)為http500服務(wù)器內(nèi)部錯(cuò)誤,從名稱(chēng)上可以理解為服務(wù)器問(wèn)題導(dǎo)致錯(cuò)誤。一般給站長(zhǎng)展現(xiàn)出的問(wèn)題分為兩種情況,一是
    發(fā)表于 07-08 09:38

    為什么單片機(jī)的主程序是死循環(huán)

    任何一個(gè)可用程序都必然是死循環(huán)程序,這不僅僅是指單片機(jī)程序。因?yàn)槿魏挝⑻幚?b class='flag-5'>器系統(tǒng)一旦開(kāi)機(jī),系統(tǒng)都在處理內(nèi)部事件和外設(shè)響應(yīng),這個(gè)過(guò)程是一個(gè)循環(huán)過(guò)程,除非關(guān)機(jī)才能結(jié)束這個(gè)死循環(huán)程序。因此,
    發(fā)表于 07-15 17:38 ?5281次閱讀

    單片機(jī)的死循環(huán)有什么作用

    單片機(jī)是可編程器件,在使用時(shí)需要編寫(xiě)滿(mǎn)足需求的程序。其C語(yǔ)言程序在各個(gè)端口、配置初始化完成后,會(huì)進(jìn)入一個(gè)死循環(huán),一般用while(1){;}的形式。初始化完成后,單片機(jī)就在死循環(huán)內(nèi)一遍又一遍的執(zhí)行程序邏輯。復(fù)位后,就從頭開(kāi)始,初始化完成后,再次進(jìn)入
    發(fā)表于 08-09 17:01 ?5671次閱讀
    單片機(jī)的<b class='flag-5'>死循環(huán)</b>有什么作用

    如何避免Xil_Assert系列宏導(dǎo)致死循環(huán)的情況

    原文標(biāo)題:【工程師分享】避免Xil_Assert系列宏導(dǎo)致死循環(huán) 文章出處:【微信公眾號(hào):FPGA開(kāi)發(fā)圈】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
    的頭像 發(fā)表于 12-02 16:20 ?4109次閱讀
    如何避免Xil_Assert系列宏<b class='flag-5'>導(dǎo)致</b>的<b class='flag-5'>死循環(huán)</b>的情況

    STM32片內(nèi)FLASH燒寫(xiě)錯(cuò)誤導(dǎo)致ST-li

    STM32片內(nèi)FLASH燒寫(xiě)錯(cuò)誤導(dǎo)致ST-li
    發(fā)表于 12-02 18:06 ?7次下載
    STM32片內(nèi)FLASH燒寫(xiě)<b class='flag-5'>錯(cuò)誤導(dǎo)致</b>ST-li

    IAR中出現(xiàn)“ the file is too large to open in the editor”

    IAR中出現(xiàn)“ the file is too large to open in the editor”出現(xiàn)此錯(cuò)誤是,先在在工程設(shè)置“Options”----》“General Options
    發(fā)表于 12-03 11:06 ?0次下載
    IAR中出現(xiàn)“ the file is <b class='flag-5'>too</b> large to <b class='flag-5'>open</b> in the editor”

    Linux服務(wù)器為什么會(huì)出現(xiàn)503錯(cuò)誤?

      瀏覽網(wǎng)頁(yè)時(shí)最常見(jiàn)的錯(cuò)誤之一是“503服務(wù)不可用錯(cuò)誤(503 Service Unavailable Error)”,此消息表明網(wǎng)絡(luò)服務(wù)器遇到技術(shù)問(wèn)題,無(wú)法處理請(qǐng)求。本文將介紹下Li
    的頭像 發(fā)表于 04-13 15:43 ?1347次閱讀

    Too many open files錯(cuò)誤導(dǎo)致服務(wù)器死循環(huán)

    服務(wù)器編程中,經(jīng)常會(huì)遇到 Too many open files 這個(gè)報(bào)錯(cuò),而且這個(gè)報(bào)錯(cuò)如果處理不好,很有可能會(huì)
    的頭像 發(fā)表于 05-23 09:08 ?2563次閱讀
    <b class='flag-5'>Too</b> <b class='flag-5'>many</b> <b class='flag-5'>open</b> <b class='flag-5'>files</b><b class='flag-5'>錯(cuò)誤導(dǎo)致</b><b class='flag-5'>服務(wù)器</b><b class='flag-5'>死循環(huán)</b>

    為什么HashMap會(huì)產(chǎn)生死循環(huán)呢?

    死循環(huán)問(wèn)題發(fā)生在 JDK 1.7 版本中,造成這個(gè)問(wèn)題主要是由于 HashMap 自身的運(yùn)行機(jī)制,加上并發(fā)操作,從而導(dǎo)致死循環(huán)。
    的頭像 發(fā)表于 12-21 09:06 ?696次閱讀
    為什么HashMap會(huì)產(chǎn)生<b class='flag-5'>死循環(huán)</b>呢?

    應(yīng)用程序中的服務(wù)器錯(cuò)誤怎么解決?

    在使用應(yīng)用程序時(shí),可能會(huì)遇到服務(wù)器錯(cuò)誤的問(wèn)題。這種錯(cuò)誤通常會(huì)導(dǎo)致應(yīng)用程序無(wú)法正常運(yùn)行 ,給用戶(hù)帶來(lái)不便。下面將介紹應(yīng)用程序中的服務(wù)器
    的頭像 發(fā)表于 03-12 15:13 ?5541次閱讀

    服務(wù)器數(shù)據(jù)恢復(fù)—SAN環(huán)境下LUN映射錯(cuò)誤導(dǎo)致寫(xiě)操作互斥失敗的數(shù)據(jù)恢復(fù)案例

    服務(wù)器數(shù)據(jù)恢復(fù)環(huán)境: SAN環(huán)境下一臺(tái)存儲(chǔ)設(shè)備中有一組由6塊硬盤(pán)組建的RAID6磁盤(pán)陣列,劃分若干LUN,MAP到不同業(yè)務(wù)的SOLARIS操作系統(tǒng)服務(wù)器上。 服務(wù)器故障: 用戶(hù)新增了一臺(tái)
    的頭像 發(fā)表于 09-26 16:31 ?214次閱讀

    服務(wù)器錯(cuò)誤是怎么回事

    服務(wù)器錯(cuò)誤通常指的是在訪問(wèn)網(wǎng)站或應(yīng)用程序時(shí),由于服務(wù)器端的問(wèn)題導(dǎo)致無(wú)法正常處理請(qǐng)求。主機(jī)測(cè)評(píng)小編為您整理發(fā)布一些常見(jiàn)的服務(wù)器
    的頭像 發(fā)表于 11-04 11:11 ?58次閱讀