0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Too many open files错误导致服务器死循环

Linux阅码场 来源:卯时卯刻 2023-05-23 09:08 次阅读

0x01 前言

在服务器编程中,经常会遇到 Too many open files 这个报错,而且这个报错如果处理不好,很有可能会导致服务器死循环。

0x02 示例代码

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

以上是我用rust写的一个非常简单的tcp服务器,它的主要逻辑是,先创建一个listener,然后再在循环里不断调用listener.accept接收tcp连接,如果接收成功,就调用handle_client处理这个连接,如果接收失败,就打印一行错误日志。

handle_client里的逻辑也非常简单,就是等待客户端关闭连接,或等待其发送任意数据,当这两种情况发生时,handle_client就会直接关闭这个连接。

当然,如果在等待期间报错了,handle_client也会打印一行错误日志。

下面我们就会使用这段程序,来演示服务器死循环的情况,这段程序不必非要用rust编写,用其他语言也都可以。

0x03 动手演示

先启动该服务器:

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

由上图可见,该服务器的进程id是312004,监听地址是0.0.0.0:9999。

再查看下该服务器已打开的文件数:

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

一共是10个,主要包括标准输入输出、epoll、及一些socket。

再查看下该服务器进程最多可打开的文件数:

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

看选中行,Soft Limit那一列,其表示该进程最多可用的文件描述符数量为1024个,即最多可同时打开的文件数为1024个。

我们把它改小一点,方便后续测试:

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

上图中,先使用prlimit命令将该服务器进程的Max open files数改成12,然后再用cat命令确认下该改动已生效。

至此,我们已经设置好该服务器进程最多可用的文件描述符数量为12,其当前已用的文件描述符数量为10,所以该服务器最多还可以再接收2个tcp连接。

我们用 `ncat localhost 9999` 命令建立连接试一下,当然你也可以用telnet, nc等其他命令,只要能建立tcp连接就行:

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

由上图服务器日志可见,该tcp连接已建立成功。

再看下当前服务器已使用的文件描述符数量:

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

由上图可见,新建socket使用的文件描述符为10,当前服务器进程已使用11个文件描述符,到目前为止一切正常。

用同样的命令再建立一个tcp连接,这次应该也能连接成功,不过会有一些有意思的事情发生:

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

首先看上图中最后一行info日志,它表示第二次tcp连接也建立成功了,如果此时去看文件描述符数量,也正好是12。

不过此次连接建立也导致不断的error日志输出,该服务器死循环了。

但此时,如果我们关闭第二次ncat命令建立的tcp连接,服务器又不会一直输出error日志了,它又会恢复到正常状态:

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

看上图中的最后一条info日志,它表示第二个tcp连接正常关闭了,且当前已建立的连接数量是1。

此时,如果我们去看文件描述符数量,其也变成了11,这里就不再截图了,有兴趣的可以自己动手试下。

0x04 为什么会出现死循环?

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

其次,linux为保证系统的整体性安全,为每个进程限制了其最大可使用的文件描述符数量,即最大可打开的文件数,这个数量就是上面我们用 `cat /proc/$(pidof too-many-open-files)/limits` 命令输出的Max open files行,Soft Limit列对应的值,该值是可以通过各种方式修改的,在我的系统上,该值默认为1024。

接着,我们启动了服务器,然后通过 `l /proc/$(pidof too-many-open-files)/fd/` 命令查看该服务器已使用的文件描述符数量,其为10。

之后,我们用prlimit命令将该服务器进程最大可使用的文件描述符数量改成了12,这样该服务器就还只剩两个文件描述符可用。

再之后,我们用ncat命令建立了两个tcp连接,在服务器端的循环里,accept接收到这两个连接并进行处理,此时该服务器进程消耗完了最后两个可用的文件描述符。

接下来,服务器代码进入下一次循环,继续调用accept尝试接收新的连接,问题的关键点也就出现在了这里。

accept是个系统调用,我们看下其对应的内核实现:

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

这个是accept系统调用的入口函数,沿着函数调用,可找到以下代码:

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

由上图可见,在真正的do_accept之前,会先调用get_unused_fd_flags找一个还未被使用的文件描述符,如果寻找时报错了,即newfd < 0,则直接返回该错误码给用户层,如果找到了一个可用的文件描述符,则开始执行真正的accept操作。

继续看get_unused_fd_flags函数:

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

它在调用其他函数之前,会通过 rlimit(RLIMIT_NOFILE) 获取当前进程最大可使用的文件描述符数量,即我们上面通过prlitmit命令设置的12。

继续往下看,我们会找到以下代码:

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

该函数的目的是分配一个文件描述符,即fd,图中选中行之前是找到一个还未被使用的fd,然后判断该 fd 是否 >= end,如果是,则goto到out,进而return error,而这个error就是EMFILE。

那end值是什么呢?它就是上面用 rlimit(RLIMIT_NOFILE) 获取的当前进程最大可用的文件描述符数。

结合上面的例子我们知道,当服务器接收完两个tcp连接后,其最大可使用的12个文件描述符已全部被用完,当其循环到下一次accept系统调用后,会最终进入到上图这个函数,这次新分配的fd值一定是12(因为fd值从0开始的,所以fd值为12表示第13个文件描述符),而我们又限制了该进程最大可用12个文件描述符,即我们限制了end值为12,所以在上图选中行进行判断时,fd 一定是 >= end 的,所以,该函数一定会返回EMFILE这个错误码。

而EMFILE是什么呢?

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

它就是我们在运行测试程序时看到的 Too many open files 这个错误。

示例程序调用accept收到这个错误码后,会打印一行error日志,然后继续循环调用accept,然后继续报错,就这样,服务器就在accept这里发生了死循环。

0x05 这个问题如何处理?

因为 too many open files 是个临时性错误,当进程中的其他地方关闭了一些文件,或者管理人员调高了该进程的 max open files值,accept就不会再报 EMFILE 错误,也就不会再死循环了。

所以其处理方法也很简单,就是在accept发生错误时,sleep一段时间,这样既防止了cpu 100%的发生,也给进程时间来调整已用及最大的文件描述符数。

0x06 用epoll也会有这个问题吗?

会有,epoll只是个通知机制,当epoll检测到有连接可被接收时,还是会通过accept来接收这个连接。

不过这里分成两种情况。

当使用epoll的edge-triggered模式时,正确写法是要一直循环调用accept接收连接,直到其返回 EAGAIN 或 EWOULDBLOCK 错误码,表示已经没有连接可接收了,这时才能退出accept循环,但如果在这之前accept返回了 too many open files 这个错误,就会发生死循环了。

当使用epoll的level-triggered模式时,可以不必一直循环调用accept直到其返回EAGAIN 或 EWOULDBLOCK,可以提前退出,但如果操作系统里还有建立好的连接等待被接收,epoll还是会一直通知应用层,告知其要调用accept接收这些连接,如果此时文件描述符没有了,accept还是会一直报 too many open files 错误,最终还是进入到了死循环。

0x07 Go是如何处理的?

下面我们看下go内置的http服务器,是如何处理这个问题的:

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

当accept返回err后,其会通过ne.Temporary()来检查该err是否是临时性错误,如果是,则会根据一定的规则,sleep一段时间。

这里,临时性错误就包括 EMFILE,即too many open files错误:

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

我们也可以写个简单的例子测试下:

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

按照之前的方式,让其触发 too many open files 这个错误:

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

由图可见,和我们上面分析的一样,其也陷入了死循环,但是它用sleep的方式,防止cpu使用率100%。

0x08 Redis是如何处理的?

下面我们看下redis是如何处理这个问题的:

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

当anetTcpAccept返回 too many open files 错误时,它只打印了一行错误日志,就直接return了。

不过因为redis使用的是level-triggered模式的epoll,所以虽然这里直接return了,但因为底层的连接没接收出来,epoll一直会调用这个函数,然后一直报错,进而死循环。

实验下:

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

可以看到,其一直在输出这个错误。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • TCP
    TCP
    +关注

    关注

    8

    文章

    1271

    浏览量

    78291
  • TELNET
    +关注

    关注

    0

    文章

    16

    浏览量

    10734
  • Rust
    +关注

    关注

    1

    文章

    223

    浏览量

    6387

原文标题:Too many open files 错误导致服务器死循环

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    独立看门狗检测及如何解决软件错误导致故障

    其用于检测和解决由软件错误导致的故障,当计数器达到设定的超时时间值时会产生系统复位。
    的头像 发表于 01-23 09:14 1.2w次阅读
    独立看门狗检测及如何解决软件<b class='flag-5'>错误导致</b>故障

    编译错误too many global/static 'bit' variables

    用的CodeVisionAVR编译,编译出错,很多都是说too many global/static 'bit' variablesError: D:\AVRproject\2014\test
    发表于 04-04 22:37

    keil下载程序时出错too many JTAG devices in chain

    keil下载程序时出错too many JTAG devices in chain
    发表于 08-20 09:30

    too many jtag devices in chain

    用keil下载程序时,出现too many jtag devices in chain,哪位大神知道怎么解决,十分感谢!
    发表于 01-20 13:46

    localhost上没有可用于服务器的活动目标

    嗨, 当我下载.bit文件时,有时当我尝试打开目标时会发生错误。信息如下。错误:[Labtoolstcl 44-494] localhost上没有可用于服务器的活动目标。目标“,jsn1”可能被
    发表于 11-01 16:07

    服务器遇到错误

    我现在经常得到这个。在您试图访问的页面上抛出了一个异常。错误已被记录,站点管理员将采取必要措施防止此类错误服务器当前可能正在经历高负载,因此请稍后通过单击浏览上的“后退”按钮再次尝
    发表于 03-06 09:33

    使用LwIP的tcp客户端和tcp服务器都结合起来使用就卡死了

    大家好,使用LwIP 的tcp 客户端和tcp服务器都没有问题,但是结合起来使用就卡死了 怎么办? 经过调试分析是卡在了tcp_active_pcbs这个链表的next指针指向了自己,所以在遍历该链表的时候就死循环了,导致卡死。
    发表于 03-18 04:22

    请问一个空for循环导致死循环如何解决?

    把for里面的语句都注释掉了,编译,下载,死循环。。。把整个for注释掉就没事,见鬼了
    发表于 04-27 04:35

    什么是服务器500错误

    500报错:被称为http500服务器内部错误,从名称上可以理解为服务器问题导致错误。一般给站长展现出的问题分为两种情况,一是
    发表于 07-08 09:38

    单片机的死循环有什么作用

    单片机是可编程器件,在使用时需要编写满足需求的程序。其C语言程序在各个端口、配置初始化完成后,会进入一个死循环,一般用while(1){;}的形式。初始化完成后,单片机就在死循环内一遍又一遍的执行程序逻辑。复位后,就从头开始,初始化完成后,再次进入
    发表于 08-09 17:01 5170次阅读
    单片机的<b class='flag-5'>死循环</b>有什么作用

    如何避免Xil_Assert系列宏导致死循环的情况

    原文标题:【工程师分享】避免Xil_Assert系列宏导致死循环 文章出处:【微信公众号:FPGA开发圈】欢迎添加关注!文章转载请注明出处。
    的头像 发表于 12-02 16:20 3825次阅读
    如何避免Xil_Assert系列宏<b class='flag-5'>导致</b>的<b class='flag-5'>死循环</b>的情况

    STM32片内FLASH烧写错误导致ST-li

    STM32片内FLASH烧写错误导致ST-li
    发表于 12-02 18:06 7次下载
    STM32片内FLASH烧写<b class='flag-5'>错误导致</b>ST-li

    哪些错误行为容易导致服务器出现严重故障?

    什么?哪些错误的使用方法很容易导致美国主机故障? 美国服务器出现严重故障的错误操作是什么? 一、美国服务器防火墙随意更换
    的头像 发表于 04-26 14:59 365次阅读

    Too many open files错误导致服务器死循环

    0x01 前言 在服务器编程中,经常会遇到 Too many open files 这个报错,而且这个报错如果处理不好,很有可能会
    的头像 发表于 05-23 09:09 855次阅读
    <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'>错误导致</b><b class='flag-5'>服务器</b><b class='flag-5'>死循环</b>

    为什么HashMap会产生死循环呢?

    死循环问题发生在 JDK 1.7 版本中,造成这个问题主要是由于 HashMap 自身的运行机制,加上并发操作,从而导致死循环
    的头像 发表于 12-21 09:06 257次阅读
    为什么HashMap会产生<b class='flag-5'>死循环</b>呢?