今天我们正在开发的游戏在测试过程中,服务器又挂了,用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 (0)
检查了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指针,让它的变成了一个未分配的内存地址,然后在新定时器加入进行插入队列的操作时候触发了段错误。