海外网站加速器,惠州网站建设外包,浙江建设信息港网站查询,企业为什么要建立战略联盟在这春风明媚的日子里#xff0c;有位T同学很苦恼。忙碌了一整天#xff0c;有个BUG愣是定位不出来。简单描述呢#xff0c;现象是这样子的#xff1a; 第一次处理是正常的#xff0c;但是后续的处理就是报错。sendto()调用错误码是 EISCONN#xff08;已被连接#xff… 在这春风明媚的日子里有位T同学很苦恼。忙碌了一整天有个BUG愣是定位不出来。简单描述呢现象是这样子的 第一次处理是正常的但是后续的处理就是报错。sendto()调用错误码是 EISCONN已被连接。 忧伤的问题 当然代码BUG的范围也很快确定了就是新加入的statsd-client-cpp工具库里。代码量不到两百行失败的地方就是在sendto()的执行里代码看这儿。一看错误码EISCONN比较少见说“socket已经被连接”——但咱这明明是UDP协议啊无连接无connect的 T同学咨询了周围的大师们翻阅了《Unix网络编程》没错这书在上次的故事里也出场了里面说 sendto()函数的执行流大约是这样子的 连接套接字输出数据报文断开套接字连接 无语了。按照圣经里说法连接都是被断开了的啊还怎么会报错“已被连接” 失败的补救 T同学比较耿直对付BUG比较直接粗暴报啥错误就解决啥错误思路有俩 人肉断开它 这个实际上是行不通的只能整个儿socket关闭销毁掉不能只断开 连接了也继续发 虽然一般情况下UDP协议的程序都是不管连接直接发sendto()的但是先连接后再write()也是可行的。 于是按照方法2搞起然而实践证明这是不给力的UDP对端根本没有收到任何数据 正确的思路 这种头痛医头、瞎试几次的做法本质上就不能算正确的方法。我们的思路应该是
收集现象分析原因验证方案解决问题 上面折腾了这么久基本上还是只有一个错误码和代码出错的位置现象数据太少了。T同学冷静下来后开始祭出大杀器strace工具程序 strace程序能够捕抓系统调用(system call)并把这些调用接口的时间、参数、返回值、耗时等等都记录下来输出信息可读性相当的高比起gdb爽多了能反映出系统最底层的运行状况是后台开发程序员的居家旅行必备的强(zhuang)大(bi)工具。 神奇的零 具体strace的过程就不多说了。调整进程数量、执行strace、查看log、研究执行状况 strace32 -s 10086 -o /tmp/Strace.log -tt -p $(pidof -s get_api_key) 终于发现一个关键现象每次的连接socket()返回值都是0 这里稍微解释一下为什么零值会是很特殊socket()返回值表示的是文件描述符在POSIX标准里有三个特殊的文件描述符值0、1、2分别是STDIN标准输入、STDOUT标准输出、STDERR标准错误。所以默认情况下零值都会作为STDIN标准输入使用只有当程序主动关闭了STDIN时系统才会分配0值给socket()使用。 所以这时候思路有两个了
假如系统分配的0值是正确的那么得寻找0值后来被“弄坏”已连接的出现地方假如系统分配的0值是错误的那么一定是某处BUG“失误”把STDIN释放掉了 这种“辩证”的思路让我忽然想起了《撸撸姐的超本格事件簿》里的给出各种伪解答搞笑助手每次破案分析都至少有正反两个思路看起来毫无盲点但总是被撸撸姐指出第三种情况哈哈哈不过在现实场景里正反两面都深入思考的做法一般帮助很大的。 谁弄坏了0 所以0是被谁弄坏的怎么弄坏的 为了深入探究这个问题得先了解一下程序运行的环境。这个工具库之前已经在现网的服务器里跑了很久也有两个简约的单元测试。都没有发现过问题。这次是在往CGI接口里使用运行环境是QZHTTPFastCGI。 所以正常情况下STDIN应该是CGI与qzhttp收包程序之间的连接用来传递HTTP请求报文。而分析到的一个现象是CGI程序的功能完全正常也就是说程序之间传递数据STDIN是正常的……等等STDIN0值不是分配给我们用吗为毛还不影响功能啊啊啊 秘诀就是——dup2(a, b)这个函数能够把一个文件描述符的信息拷贝到另一个描述符里所以文章最初出现的“EISCONN”错误的原因是 原本UDP无连接的socket被人用dup2()“篡改”了于是就变成了另外的文件描述符出现了“已连接”的状态。 写个小程序验证了一下果然能够随便拷贝到STDIN描述符里而再次strace抓包查看也发现了明显的dup调用 可以清晰看到对STDIN、STDOUT都做了dup2()操作太邪恶了火速在万能的StackOverflow.com上也找到了一篇关于0值的问答里面有人提到了如何解决这个问题那就是把0、1、2预留下来给系统老子不陪你们玩零了傲娇地离去 另一个思路 等等刚才怎么这么快就进入了“解决问题”的节奏 T同学的案例虽然因为屏幕不够长被滚动掉了但是我们要分析所有的现象找到原因啊 于是进入刚才的第二个思路是谁把STDIN给关闭了 深入strace的log发现close(0)首次出现的位置没有太特别距离后来socket()和sendto()调用很遥远暂时发现不了什么后来细想考虑时序其实是个误导。 不过因为是新引入的库导致了这个问题基本上猜测要么就是库代码有BUG要么是库和QZHTTP不兼容。重新阅读代码中与close()有关的片段尝试梳理一下思路.果然发现一个问题 d-sock是个关键的值而且没有初始化一般来说变量没有初始化会是一个随机的值但是我们的场景里StatsdClient对象是一个单体实例以static限定符实现的——所以是有初始值0的——刚好触发了BUG。 解决 再次核对代码逻辑和strace的log脑补了一下执行的流程基本上就确认BUG的原因就是这里了。 所以啊在构造函数里增加一个初始化 d-sock -1; 问题解决了。 写到这里的时候忽然想起上两周给赵总用这个工具库他也出现了一些诡异问题。当时他的现象是随机出现accept错误。现在看来BUG原因都是一个只不过赵总的使用方式是临时变量没初始化的值就是随机值因此偶然触发故障他当时引起的是accept失败也是描述符问题。而这次是必现的问题定位起来轻松一些。 思考 所以要保持良好的代码编写习惯。顺手初始化了就不会有这么纠结的问题了。