Lua Deepcopy引起的内存泄露

这些天,《攻略三国》开始导入玩家线上测试,晚上接到领导的电话,说游戏出现了反复重启的情况,急忙回到公司,ssh到服务器上,发现游戏的Logic进程占用的内存在随着时间的不断升高,最终多个进程把系统的内存占满后,被Kill掉,GameMgr检测到Logic进程挂了后会拉起进程,就出现了反复重启的情况。

上线前,压测的机器人已经是持续模拟了2000玩家同时在线操作各种业务逻辑的场景,稳定跑了超过一周的时间的,并没有出现该问题,服务器的各项指标都比较稳定。

我们的游戏服务器是一个C++ + lua的架构,2011年公司打算做网页游戏的时候,全全由我来主导开发的游戏服务器引擎。该引擎对于客户端的每一次操作都会转化成一个事件,交给Lua处理,同时在C++里会把每一个事件的处理信息记录的Log中,包括处理这个事件Lua占用的时间,Lua_state的内存增量等信息。

从Log里明显看到有一个事件ID,在每次该事件ID处理后,Lua_state的内存会增加1MB左右,一个非常恐怖的增量数字。通过ID分析,才发现是一个近期添加的新功能的刷新操作,而该功能正好也没有更新到压测的bot工具里,压测没有发现。

分析代码,发现问题的根源是同事使用了deepcopy函数导致。

--深度拷贝
function deepcopy(object)    
	local lookup_table = {}
	local function _copy(object)
		if type(object) ~= "table" then
			return object
		elseif lookup_table[object] then
			return lookup_table[object]
		end  -- if        
		local new_table = {}
		lookup_table[object] = new_table
		for index, value in pairs(object) do
			new_table[_copy(index)] = _copy(value)
		end  -- for        
		return setmetatable(new_table, getmetatable(object))    
	end  -- function _copy    
	return _copy(object)
end  -- function deepcopy

每次刷新的时候,调用该函数对一个对象进行深度拷贝。

对于一个普通的lua table来说,如果不存在交叉引用是不会有问题的。但当一个table里存在交叉引用的时候,用deepcopy就会出现严重的内存泄露,实际证明,deepcopy并不会因为有交叉引用的存在进入一个死循环导致栈溢出的异常,反而成功返回了一个拷贝对象,但拷贝的数据量非常大。打印出来看,这是一个非常deep的一个table,大概就是1MB左右。

改成其它方式后,问题就解决了。这个deepcopy函数,同事也是网上copy的,看来有些场景还是要分析清楚才能直接用。

不过最关键的还是性能测试工具没有及时的和游戏版本同步导致的。