Hi
最近在尝试优化 cosocket connect 方法的内存申请,经历如下,希望能得到大家的帮助 :)
背景:
在 ngx_lua 中 cosocket 的实现中,每个 cosocket 对象有一个 ngx_http_lua_socket_tcp_upstream_t 类型的 userdata
userdata 是由 Lua GC 管理,为了将他与 request 绑定到一起,每个 userdata 都会到 request 里注册一个 cleanup(链表里加入一个节点)
userdata 里 cleanup 指针指向 request 里的 cleanup struct handle 处;
以前每次 cleanup 执行完之后,会把 u->cleanup 和 *u->cleanup 都置为空,也就是把 userdata 里的指针置空,request 里 cleanup struct 也置空
需求:
我们的长链服务中有比较高的频率会执行一次完整的 `tcpsock:new connnect ... setkeepalive`
我们发现每次 connect 的时候,都会从 request_pool 申请一个 cleanup struct,虽然不多20多个字节,但是次数多了也就不少了,所以我们希望能复用这个 cleanup
(怎么发现的其实很好玩,用的神器 systemtap,stap++,一直想写个详细点的分享过程,无奈中间很多卡壳,还没完工)
错误的开始:
大意是:每次 cleanup 执行完之后,只是将 *u->cleanup 置空,用 *u->cleanup 为标记 userdata 是否被 cleanup 过
错误分析:
这样的错误现在看来很明显,因为 request 可能被 userdata 先被清理掉,然后实际上 *u->cleanup 这个地址指向的值已经不受控制了(会被 nginx 分配去干别的)
当 Lua GC 再来 cleanup userdata 的时候,根据我们新的规则,此时 *u->cleanup 可能就不为空了(那段地址又被用来干别的了)又会执行 cleanup 操作,就会出现 core dump:Segmentation fault
解决错误:
1. 加上更加严谨的判断:判断 connection 是否存在,是否已经关闭 (c->write->closed read->closed)
这样或许是更严谨的,还没有测试验证
2. 换个思路,从 ngx_http_cleanup_add 入手,如果有 handler 为 NULL 的就复用
但是这个对 request 的 cleanup 原有约定有较大的改动,如果有第三方模块也是按照第一种思路来复用的话,那可能就会冲突了,杯具
幸运的是,我扫描了 openresty,并未发现第一种用法,于是这个 patch 也能通过测试了
从我的业务出发,我希望是第二种,因为第二种使用场景更大,不需要保持 userdata 不被 GC,cosocket 的userdata 也有 440 个字节,也能省不少呢
又或许我们可以在 request 下挂另外一个 可以复用的 cleanup 链表,这样就肯定不会冲突了
还望春哥和其他朋友提出宝贵的意见,有没有更好的办法呢,多谢了 :)
多么痛的领悟呀:
为了查这个问题,绕了很多歪路,而且这种类型的 Segmentation fault 貌似也没有好的办法可查,打了两天日志才看出来,或许这就是小白的代价吧
以后还是尽量多看清楚代码再动手
祝好,
朱德江