从一个Bug说起:

在内部试玩时发现有个任务的玩家跟随Npc逻辑挂了.

telnet连接到出问题的设备上, 开始搞事情

这个跟随的逻辑是一个Timer驱动的. 这个Timer在主角创建时就会启动. 一开始不认为是Timer本身的问题. 怀疑是流程上各个地方有return的情况

动态改掉几个函数都没有执行到. 说明Timer的回调没有调用到. 怀疑到Timer本身来

输出了下Timer的状态, 发现是running = true. 感觉很诧异. 又看了下Timer里的其他字段发现有个handle. 输出来看了下, 发现有个removed = true.

这里就奇怪了, 说明被异常removed了. 于是开始看Timer的实现:

Timer.lua 创建一个Timer的流程如下:

1. Timer.New()

  创建元表, 基础字段初始化. running = false...

2. Timer.Start()

  1). 从UpdateBeat创建一个Handle

  通过调用UpdateBeat:CreateListener, 传入Timer.Update函数和Timer本身得到返回值Handle, 其作用是一个链表的Node.

  代码: self.handle = UpdateBeat:CreateListener(self.Update, self)

  2). 注册到UpdateBeat里:

  即把handler作为链表的node放到UpdateBeat的列表里.

  代码: UpdateBeat:AddListener(self.handle)

    3). 此时Timer类的初始化过程完成了. 后续就等着Timer.Update被调用了. 可以看到主要的内容其实是在UpdateBeat里.

  4). 要了解Handle的管理和Timer.Update的调用方式就要看UpdateBeat代码了

  5). UpdateBeat是在event.lua里定义的

       

   可以看到是一个_event的元表

  6) 关注下_event的内容:

  #1 _event.CreateListener(func, obj)

  即上面 1)里调用的函数, 这里的func就是Timer.Update, obj就是Timer自身.

  这里判断了self.keepSafe, 即5)里 定义UpdateBeat的第二个参数true. 所以这里的self.keepSafe = true.

  可以看到keepSafe的情况下, func和obj被包到了一个xfunctor里, 这个xfunctor看了下, 是一个xpcall的封装. 也就是keepSafe的event实际调用func时会用xpcall的方式执行. 出错的话会返回抛错信息.

  然后是其他的几个字段. 包括缓存了封装了Timer.Update的xpcall函数的value字段. 实现双向链表需要的两个指针(_prev, _next), 和我们要查bug需要关注的removed = true . 可以看到在Timer.Start申请Handle之初, 这个removed就是true状态的.

  #2_event.AddListener

  创建了Handle之后, 通过AddListener接口注册到链表里. 可以看到, 这里判断了self.lock 这个字段的作用是锁self.list. 即避免在操作list的时候, 有其他逻辑来改这个list.

  这个lock字段是在执行一轮Update时置为true. 执行完成一轮后改成false. 在lock为true的过程中, 如果有其他AddListener的行为, 都会把Add行为缓存到一个临时列表: self.opList里. 而不是self.list

  这个列表缓存了Add操作. 即function() self.list:pushnode(handle) end. 然后等执行一轮Update之后, 把缓存的Add的操作都执行一遍. (RemoveListener同理)

  #3 _event.__call

  这个函数是在对_event执行调用操作时执行的. 如下图 UpdateBeat()

  __call函数首先上锁 lock = true. 然后用迭代器生成器 ilist 遍历 链表list. 这个链表就是我们AddListener操作存放Timer的双向链表. (ilist详细见下方解释)

  每次迭代的 i 即为一个handle, f是xpcall包装的Timer.Update函数. 然后调用f函数, 返回xpcall的结果 成功标识: flag, 错误信息 msg. 如果出错, 则用error抛出LuaException. 并且调用_list.remove,从链表移除该Node, 并且解锁: lock = false

  (这里有个问题, 如果其中一个Timer出错了, 然后解锁了, 后面有Timer真的塞了Timer进来, 岂不是要炸?? 求解释. )

  如果一切正常情况下, 会在最后把cache的Add/RemoveListener的操作执行一遍.

  ilist迭代器可以在list.lua中看到, 根据Lua的for语句的性质. 迭代器生成器返回三个值: 迭代函数(list.next), 恒定状态(即_list), 控制变量(没用). 然后以恒定状态和控制变量为参数去调用迭代函数, 得到的返回值再赋值给 for循环的key, value (<var-list>)

  所以迭代函数为list.next. 这个函数只需要一个恒定状态(第一次是list, 其实就是头节点, 后面是每个node)即可了, 因为直接可以通过next访问到下一个. 当下一个就是头结点时, 循环结束.

  for循环定义:

   for <var-list> in <exp-list> do

list.lua:

2. 看了那么多代码, 发现控制removed = true的地方有两处: CreateListener时和执行list:remove时.
 考虑到Create是一开始调用的, 不可能一开始就挂了, 所以应该是list.remove的时候, 再看调用list.remove的地方, 有两处, 一处是RemoveListener, 一处是 _event.__call中遍历执行Update时,出错了会执行remove.
 第一处, RemoveListener时, Timer.handle.running肯定是false的. 所以肯定是第二处了, 第二处是xpcall调用的, 出问题肯定有LuaException抛出. 但是查了抛错Dump网站发现没有该用户的报错日志.
 这就尴尬了..... 后面考虑到我们做了灰度测试开关, 只有部分用户的报错信息会上传, 于是找了另外几个出问题的账号, 终于发现有个用户在反馈出错的时间, 在抛错网站上, 有个几分钟前的LuaException, 就是这个Timer的执行流程里抛的......
 于是, 结论: 出错必有LuaException, 出错找不到log, 别忘了灰度测试比例, 尽量在内部测试时放开上报比例.
 
 
PS: 在出错时 执行了timer.remove. 解开了lock, 此时removed = true, running = true. 这种操作, 是否值得商榷?
 
 
 
 

ToLua Timer机制的更多相关文章

  1. Session Timer机制分析

    Session Timer机制分析 功能介绍 会话初始化协议(SIP)并没有为所建立的会话定义存活机制.尽管用户代理可以通过会话特定的机制判断会话是否超时,但是代理服务器却做不到这点.如此一来,代理服 ...

  2. Timer&TimerTask原理分析

    转载地址,请珍惜作者的劳动成果,转载请注明出处:http://www.open-open.com/lib/view/open1337176725619.html 如果你使用Java语言进行开发,对于定 ...

  3. .NET System.Timers.Timer的原理和使用(开发定时执行程序)

    概述(来自MSDN) Timer 组件是基于服务器的计时器,它使您能够指定在应用程序中引发Elapsed 事件的周期性间隔.然后可以操控此事件以提供定期处理.例如,假设您有一台关键性服务器,必须每周7 ...

  4. tolua++实现lua层调用c++技术分析

    tolua++技术分析 cocos2dx+lua 前言 一直都使用 cocos2dx + lua 进行游戏开发,用 Lua 开发可以专注于游戏逻辑的实现,另外一方面可以实现热更新:而且 lua 是一个 ...

  5. C++ Timer

    Timer机制 这里所说的Timer机制是定时器(Timer),例如在Javascript中就提供定时执行代码的功能.但是在C++标准中暂时没有实现这一功能的函数. Javascript中的Timer ...

  6. Android 双击 Back 键退出程序

    双击退出程序的原理无非就是设置一个退出标识(询问是否退出),如果改变了这个标识(确认退出),则再次点击时立马退出,如果短时间内没有退出,则延时重置这个标识(不退出). ================ ...

  7. Javascript之旅——终点站:困惑的settimeout

    有时候结局不是很美好,但起码这也算是一种结局,这个系列的最后一篇settimeout,这是一个让人困惑的函数,也是我一直在吐槽JS的 原因,我们看不到JS的源代码,setimeout同样也是,从始到终 ...

  8. Linux系统时间与RTC时间【转】

    http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3637782 Linux的RTC驱动相对还是比较简单的,可以将它作为一个普通的字符 ...

  9. QT GUI总结

      QT提供了设计师工具,可以很方便的使用鼠标拖拽的方式绘制界面.绘制完毕后自动生成一个界面的.h文件(如ui_mainwindow.h),其中含有一个自动生成的Ui_MainWindow类,这个类中 ...

随机推荐

  1. Flume(一) —— 启动与基本使用

    基础架构 Flume is a distributed, reliable(可靠地), and available service for efficiently(高效地) collecting, a ...

  2. UML的使用

    软件工程项目这周要交一个设计文档,其中涉及UML图的画法,根据上课给的ppt做一个记录. 有关于UML的介绍在这里不再赘述,直接开整! UML的基本模型 当然必要的介绍必不可少,这里先介绍UML的基本 ...

  3. oracle 19c jdbc之Reactive Streams Ingestion (RSI) Library

    19c jdbc新特性 https://blogs.oracle.com/dev2dev/whats-new-in-193-and-183-jdbc-and-ucp jdbc实现直接路径加载 http ...

  4. npm install Error: EACCES: permission denied, mkdir

    今天研究Electron的时候,全局安装运行 npm install electron -g时侯,报下面的错误: Error: EACCES: permission denied, mkdir '/U ...

  5. App installation failed (A valid provisioning profile for this executable was not found)

    真机调试build success ,App installation failed (A valid provisioning profile for this executable was not ...

  6. ES6新增函数总结和range函数实现

    Array.from  类数组,Set,字符串转为数组 Array.of   不定参数转为数组 Array.prototype.fill(value,[start],[end]) 对数组在指定范围填充 ...

  7. aardio 文档

    aardio 文档 根据官方帮助手册制作了一份文档,添加了一些特性. 支持手机阅读 不用电脑也可以学习 aau 了,不受屏幕大小限制,你的小清新还是你的小清新~ 简单的搜索功能 快捷复制示例代码 基于 ...

  8. array_slice

    array_slice  分割数组, 效果相当于 substr 类似字符串操作

  9. [LeetCode] 93. Restore IP Addresses 复原IP地址

    Given a string containing only digits, restore it by returning all possible valid IP address combina ...

  10. [LeetCode] 244. Shortest Word Distance II 最短单词距离 II

    This is a follow up of Shortest Word Distance. The only difference is now you are given the list of ...