libuv 定时器错误使用引发的惨案

前端外刊评论 2018-03-29

今天我们正在开发的游戏在测试过程中,服务器又挂了,用gdb加载core文件后看到最后的堆栈信息如下

Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007fa57d86da66 in uv_timer_init (loop=0x7fa57da7bc80 <default_loop_struct>, handle=0x2081e28) at src/unix/timer.c:55
#1  0x000000000043d326 in Room::Room(int, bool) ()
#2  0x00000000004437e7 in RoomManager::creatRoom(std::vector<Player, std::allocator<Player> >&) ()
#3  0x0000000000491506 in AsyncCreateRoomTask::run() ()
#4  0x000000000040accf in timer_cb(uv_timer_s*) ()
#5  0x00007fa57d86df1e in uv__run_timers (loop=loop@entry=0x7fa57da7bc80 <default_loop_struct>) at src/unix/timer.c:165
#6  0x00007fa57d861f72 in uv_run (loop=0x7fa57da7bc80 <default_loop_struct>, mode=UV_RUN_DEFAULT) at src/unix/core.c:350
#7  0x0000000000409ca9 in main ()

查看libuv的源码,是下面代码引起的错误

uv__handle_init(loop, (uv_handle_t*) handle, UV_TIMER);

对应的宏定义是

#define uv__handle_init(loop_, h, type_)                                      \
  do {                                                                        \
    (h)->loop = (loop_);                                                      \
    (h)->type = (type_);                                                      \
    (h)->flags = UV__HANDLE_REF;  /* Ref the loop when active. */             \
    QUEUE_INSERT_TAIL(&(loop_)->handle_queue, &(h)->handle_queue);            \
    uv__handle_platform_init(h);                                              \
  }                                                                           \
  while ()

检查了loop和uv_timer_t均为有效指针,并且排除有多线程的竞争操作。

查看uv_timer_t的loop和type以及flags都正常赋值,于是基本锁定错误源在QUEUE_INSERT_TAIL 这个插入队列尾部的操作。

在尾部插入操作中,需要将loop的pre指向节点的next设置为uv_timer_t,但是读取loop的pre指向的是一个未分配内存的地址,然后触发了段错误。

下午几个小时经过细致的代码检查,终于找到了错误原因:libuv一个timer的结束需要调用uv_close,而原来一直只调用了uv_timer_stop,并且把uv_timer_t给delete掉了。由于没有调用uv_close,uv_timer_t还在loop的handle_queue中,在很小的机会下,系统之后又将原来这个uv_timer_t的空间分配给了其他对象,而这个对象刚好又修改到了原来handle_queue的next指针,让它的变成了一个未分配的内存地址,然后在新定时器加入进行插入队列的操作时候触发了段错误。

相关推荐