传奇版本脚本老出现脚本死循环 常见原因排查与修复处理方案

来源: 作者: 点击:
传奇版本在运行过程中出现脚本死循环,大多是脚本逻辑没有设置终止条件,或者循环判断参数异常,导致程序持续重复执行同一段代码,引擎占用持续升高,严重时会造成卡顿、闪退甚至直接断开连接。很多人遇到这类问题只会直接重启服务端,没有找到根源,重启之后过一段时间依旧会再次触发死循环,下面把常见诱因、排查方法以及对应的修改方式完整说明。

脚本死循环最常见的一类是while循环缺少退出条件。编写循环逻辑的时候,需要设置一个变量,每执行一次循环变量就发生变化,当变量达到指定数值,循环就停止。如果忘记写变量自增或者自减语句,条件永远成立,代码就会无限重复运行。比如写怪物刷新、物品检测的脚本,while判断条件一直为真,没有break跳出语句,引擎就会卡在这段脚本不断执行。还有部分写法把判断条件写死,不会随着脚本运行发生改变,无论执行多少次,判断结果都不变,同样会触发循环卡死。

第二是循环内部调用自身脚本造成递归死循环。很多版本脚本会使用调用脚本命令,在一段脚本里面又再次调用自己,没有设置递归次数上限。在触发条件满足的情况下,会一层一层持续调用,没有停止节点。这类情况多出现在人物登录检测、称号触发、拾取物品、英雄相关脚本里面。一旦玩家或者怪物持续触发事件,脚本不断重复调用,堆栈持续累积,最后出现卡死。还有部分脚本跨文件互相调用,A脚本调用B脚本,B脚本又回过来调用A脚本,形成循环调用,表现和死循环一致,排查的时候很容易被当成普通while循环问题。

第三是循环内的条件判断被外部数据干扰。脚本读取人物变量、怪物数量、地图标记等数据做判断,如果读取的数据不会正常更新,循环条件就不会失效。例如脚本持续检测地图内怪物数量,怪物清除之后标记变量没有同步重置,脚本依旧判定怪物存在,继续执行刷怪逻辑。还有人物离线之后,人物变量没有及时清理,后台循环脚本还在持续读取该人物数据,反复执行任务逻辑。变量类型不匹配也会带来问题,数字变量和字符串混用,判断结果永远为true,循环无法终止。

第四是定时器、持续触发事件搭配不当引发隐性死循环。定时器脚本设置间隔时间太短,同时定时器内部又开启循环代码,上一轮循环还没有执行完毕,下一轮定时器又被触发,多个循环叠加在一起,服务端资源占用持续上涨。还有地图触发、走路触发、打怪触发类脚本,触发事件里面嵌套循环,玩家持续移动或者攻击,脚本就会不停启动循环段。这类隐性死循环不会立刻卡死,往往是开区一段时间之后,在线人数变多才逐步暴露,前期测试很难发现。

第五是循环内执行耗时操作,阻塞引擎主线程。循环代码里面大量使用读取文件、遍历大量物品、遍历地图全部怪物的语句,单次循环执行时间过长,加上循环次数没有限制,引擎主线程持续被占用,其他事件无法响应。很多人会误以为是死循环,本质是循环没有卡死,但是单次运算量过大。这类情况需要区分,查看引擎日志,看脚本是否持续重复输出同一行日志,如果日志不停重复打印相同内容,就是标准死循环,如果日志输出缓慢但有序,属于循环运算过载。

排查脚本死循环可以按照固定步骤操作。第一步开启引擎脚本日志记录功能,脚本卡死的时候查看日志文件,找到持续重复打印的那一段内容,定位到对应的脚本文件和行数。日志里面反复出现的命令,就是死循环所在代码块。第二步注释掉怀疑有问题的循环代码,重新加载脚本,测试是否还会出现卡死,以此确认问题代码位置。第三步单独提取这段循环脚本,放到测试脚本里面单独运行,模拟触发条件,复现问题,不要直接在正式大区测试修改。第四步查看变量变化情况,打印循环内用到的判断变量,观察变量是否按照预期发生改变,没有变化就是缺少变量更新语句。

找到问题代码之后,对应的修改方法。针对while循环缺少终止条件,在循环体内增加变量增减语句,同时设置最大循环次数保护,就算业务条件异常,到达次数强制跳出循环。可以在循环开头增加计数变量,每循环一次计数加一,计数超过上限就执行break命令退出循环,作为兜底保护。针对递归调用,增加递归层数判断,限制最大调用次数,不要无限嵌套调用脚本。互相调用的多个脚本,调整触发逻辑,取消双向调用结构,改为单向事件触发。

处理变量异常导致的死循环,在循环开始之前先对变量做初始化,每次执行完毕之后重置标记变量。读取人物、怪物数据的时候增加数据有效性判断,判断对象是否存在,对象不存在就直接终止循环。区分本地临时变量和全局变量,全局变量在循环结束之后及时清空,避免残留数据影响下一次触发。变量比较的时候统一数据类型,数字和字符串分开判断,防止类型错误造成判断恒成立。

定时器和事件触发脚本优化,拉长定时器执行间隔,不要设置毫秒级短间隔。定时器内部尽量不要嵌套循环,把大量循环逻辑拆分,减少单次执行运算量。地图触发、拾取触发这类高频事件,脚本开头增加冷却标记,短时间内重复触发直接返回,避免短时间多次启动循环。人物离线、怪物死亡等场景,及时关闭对应的后台循环,对象销毁同步终止脚本运行。

循环内部大量遍历数据的脚本,拆分遍历逻辑,分多次执行,不要一次性遍历全部对象。减少循环内读写外部文件、数据库的操作,这类操作耗时高。循环里面减少不必要的信息打印,大量日志输出也会加重引擎负担。脚本修改完成之后,先在单机环境反复模拟场景测试,确认不再出现循环卡死,再放到大区使用。

日常维护阶段,提前做好脚本防护,降低死循环出现概率。所有循环代码都要增加最大次数兜底,不要只依靠业务条件退出。写脚本的时候养成打印关键变量的习惯,方便后续排查。版本更新新增脚本之后,单独做压力测试,模拟多玩家同时触发脚本场景。定期查看引擎资源占用和脚本日志,发现日志持续刷屏,提前定位处理,不要等到服务器卡顿再排查。

脚本死循环属于版本开发里比较常见的逻辑错误,根源大多是没有做好边界判断和退出保护。只要找准循环代码位置,补齐终止条件,增加循环次数限制,规范变量使用方式,就可以解决大部分同类问题。修改脚本时不要一次性大范围改动代码,分段测试,防止改动之后引入其他新的脚本异常。