您现在的位置是:人工智能 >>正文
聊聊 Too Many Open Files 错误导致服务器死循环
人工智能5人已围观
简介0x01 前言 在服务器编程中,经常会遇到 Too many open files 这个报错,而且这个报错如果处理不好,很有可能会导致服务器死循环。0x02 示例代码 以上 ...
在服务器编程中 ,聊聊经常会遇到 Too many open files 这个报错,错误而且这个报错如果处理不好,服务很有可能会导致服务器死循环。器死
0x02 示例代码
以上是循环我用rust写的一个非常简单的tcp服务器,它的聊聊主要逻辑是,先创建一个listener ,错误然后再在循环里不断调用listener.accept接收tcp连接,服务如果接收成功 ,器死就调用handle_client处理这个连接 ,循环如果接收失败 ,聊聊就打印一行错误日志。服务器租用错误
handle_client里的服务逻辑也非常简单 ,就是器死等待客户端关闭连接 ,或等待其发送任意数据,循环当这两种情况发生时,handle_client就会直接关闭这个连接。
当然 ,如果在等待期间报错了,handle_client也会打印一行错误日志 。
下面我们就会使用这段程序,来演示服务器死循环的情况,这段程序不必非要用rust编写 ,用其他语言也都可以。
测试代码我已经放到github了,免费模板如果想要自己动手测试的 ,可以clone下来自己试下 。
代码地址:https://github.com/ytcoode/too-many-open-files
0x03 动手演示先启动该服务器 :

由上图可见,该服务器的进程id是312004,监听地址是0.0.0.0:9999。
再查看下该服务器已打开的文件数 :

一共是10个 ,主要包括标准输入输出、epoll、及一些socket 。
再查看下该服务器进程最多可打开的源码库文件数:

看选中行,Soft Limit那一列,其表示该进程最多可用的文件描述符数量为1024个,即最多可同时打开的文件数为1024个。
我们把它改小一点 ,方便后续测试 :

上图中,先使用prlimit命令将该服务器进程的Max open files数改成12 ,然后再用cat命令确认下该改动已生效。
至此 ,我们已经设置好该服务器进程最多可用的文件描述符数量为12 ,其当前已用的文件描述符数量为10,香港云服务器所以该服务器最多还可以再接收2个tcp连接。
我们用 `ncat localhost 9999` 命令建立连接试一下,当然你也可以用telnet, nc等其他命令 ,只要能建立tcp连接就行:

由上图服务器日志可见 ,该tcp连接已建立成功。
再看下当前服务器已使用的文件描述符数量 :

由上图可见 ,新建socket使用的文件描述符为10 ,当前服务器进程已使用11个文件描述符,到目前为止一切正常。
用同样的命令再建立一个tcp连接,这次应该也能连接成功 ,不过会有一些有意思的事情发生:

首先看上图中最后一行info日志,亿华云它表示第二次tcp连接也建立成功了,如果此时去看文件描述符数量 ,也正好是12 。
不过此次连接建立也导致不断的error日志输出,该服务器死循环了。
但此时,如果我们关闭第二次ncat命令建立的tcp连接,服务器又不会一直输出error日志了,它又会恢复到正常状态:

看上图中的最后一条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是个系统调用,我们看下其对应的内核实现 :

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

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

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

该函数的目的是分配一个文件描述符,即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是什么呢?

它就是我们在运行测试程序时看到的 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服务器 ,是如何处理这个问题的 :

当accept返回err后 ,其会通过ne.Temporary()来检查该err是否是临时性错误,如果是,则会根据一定的规则,sleep一段时间。
这里,临时性错误就包括 EMFILE ,即too many open files错误:

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

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

由图可见,和我们上面分析的一样 ,其也陷入了死循环,但是它用sleep的方式,防止cpu使用率100%。
0x08 Redis是如何处理的 ?下面我们看下redis是如何处理这个问题的:

当anetTcpAccept返回 too many open files 错误时,它只打印了一行错误日志 ,就直接return了。
不过因为redis使用的是level-triggered模式的epoll ,所以虽然这里直接return了,但因为底层的连接没接收出来,epoll一直会调用这个函数,然后一直报错,进而死循环。
实验下:

可以看到,其一直在输出这个错误 。
0x09 结语希望通过这篇文章,能给大家的技术水平带来一点提高 。

Tags:
转载:欢迎各位朋友分享到网络,但转载请说明文章出处“信息技术视野”。http://www.bziz.cn/html/056c8999854.html
相关文章
ChatGPT和生成式AI强化零信任的十种方式
人工智能网络安全CEO从客户处得到的反馈如何?在2023年RSA大会上接受采访的网络安全提供商的CEO表示,他们的企业客户认可ChatGPT在改善网络安全方面的价值,同时也表达了对机密数据和知识产权意外泄露风 ...
【人工智能】
阅读更多以美妆PPT制作教程电脑为主题(打造精美妆容PPT,让你成为妆容达人)
人工智能在现代社会,美妆已经成为了很多人日常生活中不可或缺的一部分。而制作一份精美的美妆PPT,不仅可以帮助我们展示自己的妆容技巧,还能够吸引更多人的关注和学习。本文将以电脑为主题,为大家分享一些关于美妆PP ...
【人工智能】
阅读更多苹果XR电脑升级iOS教程(简明易懂的操作指南,让你的苹果XR电脑焕发新生)
人工智能苹果XR是一款性能出色的智能手机,升级最新的iOS系统可以为用户带来更好的使用体验和功能。本文将详细介绍如何升级苹果XR电脑的iOS系统,以便用户能够更好地掌握操作方法。一:备份数据关键字:数据备份) ...
【人工智能】
阅读更多
热门文章
最新文章
友情链接
- 探索如何将锁屏变成个性主题(打造独特的手机锁屏体验)
- 探索SonyVaioVPCEA28EC的功能与性能(一款值得关注的笔记本电脑选择)
- IBM携手腾讯联合发布《无边界零售》白皮书:洞察行业新格局,赋能企业“无边界零售”转型
- 京品电源(京品电源的)
- 以里程音箱如何给你带来震撼音质体验?(探索里程音箱的创新技术与卓越性能)
- 探究惠普导航的优势与应用(解密惠普导航的功能和使用体验)
- 新华三亮相2022全球工业互联网大会 展现“新硬件”创新实力
- 小米膜的优点和特点(为你的手机屏幕保驾护航)
- 体验XboxOneX的卓越性能与极致画质(探索一台引领游戏娱乐新纪元的游戏机) 亿华云企业服务器香港物理机网站建设源码库b2b信息平台云服务器