请教一个使用lua模块的nginx出现个别进程luajit内存占用过大的问题

346 views
Skip to first unread message

mingy...@gmail.com

unread,
Aug 26, 2014, 2:31:01 AM8/26/14
to open...@googlegroups.com
你好,春哥

我们的系统使用了nginx的lua模块,通过luajit的ffi来调用一个c库的一个接口对数据做一些处理,然后再返回给前端。目前在线上发现系统运行很长时间之后偶尔会有个别nginx进程出现内存占用很大的情况,在error log中报以下错误:

failed to load external Lua file: memory allocation error

我通过nginx-gdb-utils的lgcstat调试了一下,发现luajit使用的内存已达1GB上限,具体如下:

(gdb) lgcstat
28901 str objects: max=2790981, avg = 15668, min=18, sum=452825982
8472 upval objects: max=24, avg = 24, min=24, sum=203328
2070 thread objects: max=2464, avg = 2462, min=568, sum=5096952
119 proto objects: max=3284, avg = 436, min=92, sum=51894
85462 func objects: max=144, avg = 27, min=20, sum=2320504
63 trace objects: max=2076, avg = 499, min=160, sum=31444
44970 cdata objects: max=600, avg = 39, min=12, sum=1764100
34766 tab objects: max=6176, avg = 121, min=32, sum=4236000
114826 udata objects: max=528396, avg = 5144, min=27, sum=590741282

sizeof strhash 131072
sizeof g->tmpbuf 0
sizeof ctype_state 4568
sizeof jit_state 6024

total sz 1057418310
g->strnum 28901, g->gc.total 1060209274

从结果看来userdata和str占用空间最多,分别占用了590MB和452MB(这里tmpbuf的大小为0是因为我这里使用的时候会报一个找不到成员e的错误,因此我把tmpbuf相关的代码注释掉了)。请问一下接下来有什么思路可以继续追查下去。

以下是我们的简化后的相关代码,麻烦看下是不是使用上有什么问题:
我们调用的这个接口是这样的:

typedef struct
{
int a;
char b[256];
...
} foo;

int func(const void *in_data, const size_t in_data_len, foo *f, void **out_data, size_t *out_data_len);

我们是这样调用的:

local foo = ffi.new("foo[1]")
foo[0].a = ...;
foo[1].b = ...;

local in_data = ...;
local in_data_len = ...;
local out_data = ffi.new("void*[1]")
local out_data_len = ffi.new("size_t[1]", 0)

local ret = lib.func(in_data, in_data_len, foo, out_data, out_data_len)

local final_data = ffi.string(out_data[0], out_data_len[0])
ffi.C.free(out_data[0])

我们使用的是luajit2.0.3

谢谢!

Cen Zheng

unread,
Aug 26, 2014, 2:48:33 AM8/26/14
to open...@googlegroups.com

红字处笔误
在 2014年8月26日星期二UTC+8下午2时31分01秒,Cen Zheng写道:
foo[0].b = ...;

Yichun Zhang (agentzh)

unread,
Aug 27, 2014, 3:04:57 PM8/27/14
to openresty
Hello!

2014-08-25 23:31 GMT-07:00 mingyan.zc:
> 从结果看来userdata和str占用空间最多,分别占用了590MB和452MB(这里tmpbuf的大小为0是因为我这里使用的时候会报一个找不到成员e的错误,因此我把tmpbuf相关的代码注释掉了)。请问一下接下来有什么思路可以继续追查下去。
>

你提供的代码片段会产生 Lua string 对象(通过 ffi.string)和 cdata 对象(通过 ffi.new
之类),但并没有产生 userdata 的地方。而 userdata 对象占的内存最大,而 cdata
的内存使用其实是很小的。你或许应当检查一下这些 userdata 的来源(你可以修改 lgcstat 命令的 Python 实现,对
userdata 对象的内部内容进行检查)。

另外,我看到你有一些很大的 Lua string 对象,或许你可以修改你的处理模式,尽量采取按块流式处理的做法,以避免创建很大的 Lua
string 对象。特别地,你可以使用 lgcpath 工具检查最大的那些还活着的 Lua string
对象的内容,如果你不能确定它们的来源的话:

https://github.com/openresty/nginx-gdb-utils#lgcpath

或许我们还可以让 lgcpath 输出像 lgcstat 那样的全局统计信息,这样有助于确定究竟是内存泄漏呢还是 GC 来不及释放垃圾。欢迎贡献补丁 :)

另外,tmpbuf 之所以在你那儿会有问题是因为这个工具主要是针对 LuaJIT v2.1 的,而 LuaJIT 2.0.x 在
tmpbuf 的实现上并不相同。

> 以下是我们的简化后的相关代码,麻烦看下是不是使用上有什么问题:

没有看出有什么明显的问题。你能弄一个能复现该问题的最小化的完整用例吗?

Best regards,
-agentzh

Cen Zheng

unread,
Aug 27, 2014, 11:07:04 PM8/27/14
to open...@googlegroups.com
谢谢回复!

在 2014年8月28日星期四UTC+8上午3时04分57秒,agentzh写道:

> 你提供的代码片段会产生 Lua string 对象(通过 ffi.string)和 cdata 对象(通过 ffi.new
> 之类),但并没有产生 userdata 的地方。而 userdata 对象占的内存最大,而 cdata
> 的内存使用其实是很小的。你或许应当检查一下这些 userdata 的来源(你可以修改 lgcstat 命令的 Python 实现,对
> userdata 对象的内部内容进行检查)。

 
找了半天也没找到userdata和cdata的区别,是否cdata是luajit ffi里独有的类型,而userdata则是指使用lua的C API创建的?如果是这样那我的代码应该没有才是。
目前我对于lua的内部实现基本属于空白状态,因此“修改 lgcstat 命令的 Python 实现,对 userdata 对象的内部内容进行检查”这个对我来说难度较大,能否给些建议,是否需要先阅读luajit的源码?

>另外,我看到你有一些很大的 Lua string 对象,或许你可以修改你的处理模式,尽量采取按块流式处理的做法,以避免创建很大的 Lua
>string 对象。特别地,你可以使用 lgcpath 工具检查最大的那些还活着的 Lua string
>对象的内容,如果你不能确定它们的来源的话:

>    https://github.com/openresty/nginx-gdb-utils#lgcpath

 
关于lua string,你所说的“按块流式处理的做法”具体是怎么做?我的应用场景里其实是对图片做处理,因此输入和输出都需要是完整的图片数据。
lgcpath我使用过,但是对于绝大多数我输入的size,输出都是:

“No GC object of size xx”

偶尔成功的时候还会抛类似这样的一个错误:

(gdb) lgcpath 200 str 
Python Exception <class 'gdb.error'> Cannot convert value to int.: 
path 000:[registry] ->Tab["crypto.x509"] ->Tab["__gc"] ->cfunc ->env ->Tab["path"] ->str "/home/admin/nginx-imgsrc/conf/image_conf/?.lua;/ ...") Error occurred in Python command: Cannot convert value to int.

>没有看出有什么明显的问题。你能弄一个能复现该问题的最小化的完整用例吗? 

 要复现这个问题比较困难,我们的系统机器数量很多,并且需要运行很久才偶尔会出现一例。

Yichun Zhang (agentzh)

unread,
Aug 28, 2014, 3:00:21 PM8/28/14
to openresty
Hello!

2014-08-27 20:07 GMT-07:00 Cen Zheng:
>
> 找了半天也没找到userdata和cdata的区别,是否cdata是luajit ffi里独有的类型,而userdata则是指使用lua的C
> API创建的?

是的。你使用的第三方的 Lua C module(我看到你的提供的 lgcpath 的输出里显示 Lua 的 registry 里面有
crypto.x509 这样的东西,应当是某个第三方 Lua C 模块引入的),以及 ngx_lua 模块本身有可能创建 user
data.

> 如果是这样那我的代码应该没有才是。

见上。

> 目前我对于lua的内部实现基本属于空白状态,因此“修改 lgcstat 命令的 Python 实现,对 userdata
> 对象的内部内容进行检查”这个对我来说难度较大,能否给些建议,是否需要先阅读luajit的源码?
>

不用通读 LuaJIT 的实现代码,只需找到 lgcstat 命令的 Python 实现里统计 userdata 的地方,然后把符合条件的
userdata 数据结构里的内存内容 dump 出来就好了。或许 userdata 内部的数据内容会让你猜出来源。我看到这些
userdata 都很大,平均都有 5KB.

>
> 关于lua string,你所说的“按块流式处理的做法”具体是怎么做?我的应用场景里其实是对图片做处理,因此输入和输出都需要是完整的图片数据。

那就没戏了。

值得一提的是,lgcpath 也是针对 LuaJIT v2.1 的实现编写的,我不清楚是否能在 LuaJIT 2.0.x
里正常工作。建议升级到最新的 LuaJIT v2.1.

另外,你未提及你使用的 ngx_lua 模块的版本。如果不是最新版的话,建议升级之。

Regards,
-agentzh
Reply all
Reply to author
Forward
0 new messages