# lua变量缺少local造成unity死锁
(金庆的专栏 2020.9.1)
这几天在开发 grpc-tolua, 可以在 unity 项目中使用 lua 发送 grpc 请求,无需生成代码.
现已实现 route_guide 示例中的全部4种rpc.
见:https://github.com/jinq0123/grpc-tolua
昨天的开发中就发现,运行 RecordRoute 和 RouteChat 之后Unity无法再次运行,必须杀进程。
在 await 语句添加 `ConfigureAwait(false)` 之后,好像 RouteChat 修复了,但 RecoreRoute 依旧。
ConfigureAwait(false) 保证了 await 之后不会抢占主线程,是解决死锁的一种方法。
grpc 源码中的 await 同样都跟着这句。
今天再次测试,发现 RouteChat 并未真正修复,仍有较大可能卡死。
最初的猜想是 Lua 协程,C# 异步造成线程死锁,因为对异步不熟,总是怀疑使用有误。
后来转变为查找让 Unity 卡死的阻塞式操作,但是因为日志显示不全,找遍代码都没结果。
除了在自己的代码中查找错误,还在 tolua 代码,grpc 代码中查找,到处怀疑。
今天一整天都在添加日志,重启Unity中度过。提出各种猜想,又反复验证。
依次采用了以下方法:
* 比较C# route_guide 代码,查找区别
* 为什么是 client streaming 类型的 rpc 有错
* 查看 tolua 协程,Timer
* 添加 Delay, 更改协程的运行次序,提高或降低复现几率
* 函数入口和返回添加日志,证明其没有阻塞
查找错误的方法是在代码中添加日志,找到异常之处。
最终的现象是Unity没有Update()调用了,全部卡死。
错误源代码是这样的,错误版本中 awaiter 前面没有 local.
```lua
local function await(awaitable)
local awaiter = awaitable:GetAwaiter()
coroutine.wait_until(function()
return awaiter.IsCompleted
end)
end
```
因为想实现一个特殊版本的 await() 用于测试, 把 awaiter.IsCompleted 改为总是 false。
复制代码时,发现了变量缺少 local.
如果这个成为全局变量,第2次函数调用就会更改第1次调用的变量,我立刻意识到了这就是死锁的原因。
添加 local 之后测试,一切都正常了。
分析结果是因为 awaiter 更改后,await() 在任务完成前就返回了,
以致于后面的读取成为阻塞式操作,阻塞了主线程。
查错效率低的主要问题是 Unity 日志在卡死前的日志没有显示,这一点直到最后死锁复盘时才发觉。
因为日志缺失,根据日志得出的所有判断几乎都错了。
本来可以根据日志快速定位的原因,现在完全找错方向。
如果有工具警告全局变量,就可以避免这个错误。
Unity Profiler, VS Debugger 因为 Unity 完全卡死,所以没什么帮助。