Hello!
2013/1/24 chuang:
> 我看了下ngx_lua的代码,在可能阻塞的I/O操作比如网络收报中(对应函数是ngx_http_lua_socket_tcp_receive),如果当前没有数据则会调用lua_yield切换出去。Lua中不能在C代码中直接yield,否则会报“"attempt
> to yield across metamethod/C-call boundary”的错,不知道你们是怎么做到的?
>
貌似你对这个错误信息的理解有偏差。
Lua 5.1 和 LuaJIT 2.0 允许在 CFunction 里直接调用 lua_yield(),但并不允许 coroutine
yield 跨越 C 函数调用的边界,或者换句话说,不允许通过 lua_yield() 或者 coroutine.yield()
强行中断一个正在运行中的 C 函数的执行。
值得一提的是,在 LuaJIT 2.0 中这个错误出现的机率更小一些,因为像 pcall 和 xpcall 这样的 Lua 原语并没有实现为
C 函数,所以 yield 是可以打断外层的 pcall 或者 xpcall 调用的。
> 模拟一个场景,在C中创建出coroutine来执行Lua脚本,并且提供C
> API给Lua使用,当某些操作可能会阻塞时(如网络I/O),C函数中执行yield将协程切换出去,然后未来的某个时刻,如果条件满足则resume继续执行后面的脚本.我写了个demo程序是这样的:
>
> co.c:
> #include <stdio.h>
> #include "lua.h"
> #include "lualib.h"
> #include "lauxlib.h"
>
> static int panic(lua_State *state) {
> printf("PANIC: unprotected error in call to Lua API (%s)\n",
> lua_tostring(state, -1));
> return 0;
> }
>
> static int test(lua_State *state) {
> printf("in test\n");
> lua_yield(state, 0);
> printf("after in test\n");
> return 0;
> }
>
> int main(int argc, char *argv[]) {
> char *name = NULL;
> name = "co.lua";
> lua_State* L1 = NULL;
> L1 = lua_open();
> lua_atpanic(L1, panic);
> luaL_openlibs( L1 );
>
> lua_register(L1, "test", test);
> lua_State* L = lua_newthread(L1);
>
> luaL_dofile(L, name);
> sleep(1);
> lua_resume(L, 0);
> printf("after resume test\n");
>
> return 0;
> }
>
> co.lua:
> print("before")
> test("123")
> print("after resume")
>
> 问题在于,resume之后,在co.lua中的"after resume"没有打印出来
>
这个示例代码本身是有问题的;Lua coroutine API 的用法是错误的。下面给出我修改过后的版本:
#include <stdio.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
static int panic(lua_State *state) {
printf("PANIC: unprotected error in call to Lua API (%s)\n",
lua_tostring(state, -1));
return 0;
}
static int test(lua_State *state) {
printf("in test\n");
printf("yielding\n");
return lua_yield(state, 0);
}
int main(int argc, char *argv[]) {
char *name = NULL;
name = "co.lua";
lua_State* L1 = NULL;
L1 = lua_open();
lua_atpanic(L1, panic);
luaL_openlibs( L1 );
lua_register(L1, "test", test);
lua_State* L = lua_newthread(L1);
luaL_loadfile(L, name);
lua_resume(L, 0);
printf("sleeping\n");
sleep(1);
lua_resume(L, 0);
printf("after resume test\n");
return 0;
}
现在使用 LuaJIT 2.0 和 Lua 5.1.5 都可以得到期望的输出了:
$ ./a.out
before
in test
yielding
sleeping
after resume
after resume test
同时抄送给 openresty 中文邮件列表:http://openresty.org/#Community
建议你加入此列表并在那里和我们交流这样的问题。谢谢合作 :)
Best regards,
-agentzh