前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一次并发Bug

一次并发Bug

作者头像
重归混沌
发布2020-06-16 16:12:22
3920
发布2020-06-16 16:12:22
举报
文章被收录于专栏:重归混沌

最近碰到一个bug, 约7天发生一次,历时3周才找到问题并修复。

在找到bug并修复后,我仔细反思了一下。

我发现不仅这个bug是并发bug的一个典型,修复bug的过程也是直击我的弱点。

下面记录一下整个过程。

最近突然想对我的网络框架做一次RPC浸泡测试。

写了一段代码,开了6000个客户端,去并发RPC请求,服务端收到RPC之后会随机10~2500ms 延时之后再返回。丢到我的VPS上之后,也没在意,过了两天就忘记这回事了。

过了大概一周,我突然想起来我还有一个浸泡测试在跑着呢,就上去看了一下log.

令我意外的是,网络和rpc没有出现bug,定时器出现Bug了。

就这样一边加log等重现,一边看代码,在历时3周后终于靠看代码修掉了。

定时器相关代码大概如下:

--core.lua(run in thread1)

function core.timeout(ms, func) local co = cocreate(func)

--silly_timeout 等价于 silly_timer_timeout local session = silly_timeout(ms)

sleep_session_co[session] = co sleep_co_session[co] = session

return session

end

local MSG = {}

function MSG.expire(session, _, _) local co = sleep_session_co[session] assert(sleep_co_session[co] == session) sleep_session_co[session] = nil sleep_co_session[co] = nil wakeup_co_queue[#wakeup_co_queue + 1] = co

end

//silly_timer.c

uint32_t silly_timer_timeout(uint32_t expire){ struct node *n = newnode(); if (unlikely(n == NULL)) { silly_log("silly timer alloc node failed\n"); return -1; } lock(T); n->expire = expire / TIMER_RESOLUTION + T->ticktime; assert((int32_t)(n->expire - T->expire) >= 0); add_node(T, n); unlock(T); return n->session;

}

// run thread2

void silly_timer_update()

{

//检查所有struct node, 是否超时

//如果超时,则向Lua层抛出expire消息

//然后将node结点Free掉

//在Lua层收到expire消息后,会调用MSG.expire函数

}

发现bug时,是发现在MSG.expire函数的第二行assert失败了。

这时我就告诉自己,柯南君的名言“排除一切不可能,剩下的再不可能也是真相”。

即然assert失败了,那只有三种可能。

A1. co 为 nil, 这时sleep_co_session[co]为nil A2. co 不为nil 且sleep_co_session[co] 为nil A3. co 不为nil 且 sleep_co_session[co]不等于session

我检查了一下代码,发现sleep_session_co 和sleep_co_session都是一起操作的,因此我将A2排除了。

为了进一步排除A1和A3, 我在MSG.expire加了点log,当assert失败时,打印一下session和co的值。

过了几天后确定是A1.

而导致A1产生的可能情况有以下几种可能:

B1. silly_timer_timeout返回了两个相同的session B2. silly_timer_update 对同一个session返回了2次 B3. silly_timer_update 返回了一个陌生的session

我查了一下rpc client端的log,我发现在相同的时间确实有一次rpc请求失败了,排除了B2.

如果是情况3, 那就代表silly_timer_update时返回的是脏数据,我查看了silly_timer.c中的代码,发现node->session只有在newnode时被赋值,其他情况并没有被改动过。因此我将B3排除了。

现在就剩下B1,出现B1 有两种可能。

C1. session回绕了 C2. 在++session时有并发问题出现了某种竞争

于是,我在session回绕和session每增加1000时,分别打印了一行log。以期望可以在下次重现时,获得一些有用的信息。

再次出现之后,我发现session并没有回绕,而session每增加1000打印的log似乎对我并没有什么帮助。

我开始把矛头对准C2分析。但是我反复检查,以我现有的知识来讲,session都不可能存在并发问题。

线突然断了,我知道一定是我哪里弄错了,但是我思维已经造成了定式。反复思考也没看到哪里有问题。

之后,每天打开电脑第一件事,就是去看一遍之前发生过的log和代码,以便突然有点灵感。

在过了大约7天之后,我突然发现,session每增加1000时的log L1和assert失败时的log L2发生在同一秒,而出错的session比L1中打印出来的session要小。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 重归混沌 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档