a-memory-leak-event

#-python #-memory #leak 编辑

上段时间组内解决的一个奇葩内存泄露问题, 找到最终原因时发现真是奇坑无比的一个原因.

一开始怀疑某个逻辑会导致dict引用数一直增加, 或是其他对象始终不释放, 导致内存一直在涨, 期间开启gc collect也没用. 使用memory grapher 等内存泄露工具检测, 打印出一段时间对象的增长量, dict, list等数据结构的增长量, 从打印信息看虽然有一定问题, 但是确实不可能会导致那么严重的内存泄露, 我们某个游戏服有时甚至会突然从300MB进程内存, 很快就上涨到3GB.

最后只好从业务层面, 把那些请求量大的接口, 逐个拿来测试, 排除法, 但是居然无论是哪个接口, 排除到最后即使一个非常简单的逻辑, 同样会有内存泄露, 简直无法相信自己的眼镜.

最后的最后, 发现在框架的最外围代码里, 有一处gsignal(signal.SIGTERM, exit, server) 代码, 当时应该是为了方便测试, 捕获ctrl+c等信号, 以便快速使各coroutine迅速退出.

但是问题在于这3行signal代码, 写在app中, 就是gevent的 StreamServer构造好之后, 每次有请求来都会回掉的那个主入口函数.

server = StreamServer(('0.0.0.0', port), apps, spawn=pool)

....

def apps(socket, address):
    # this handler will be run for each incoming connection in a dedicated greenlet
    # using a makefile because we want to use readline()
    gsignal(signal.SIGTERM, exit, server)
    gsignal(signal.SIGQUIT, exit, server)
    gsignal(signal.SIGINT, exit, server)
    socket.setsockopt(origin_socket.IPPROTO_TCP, origin_socket.TCP_NODELAY, 1)
    port = int(sys.argv[1])

...

这段代码会导致只要有socket回调, 就创建3个信号处理标记, 对象是只增不减. 没想到当初随便的一记只为测试方便的代码, 导致后来一直的内存泄露.