文章目录

  在调用lua的yield时,我们可能会遇到”attempt to yield across metamethod/C-call boundary”(lua5.2或5.3为”attempt to yield across a C-call boundary”)这个报错,这报错到底是什么意思呢?又是什么原因引起的这个报错呢?

  首先lua中的主协程不能被yield,如果你试图yield主协程,就会收到这个报错,当然如果是在lua5.2或者lua5.3的版本中,yield主协程有个单独的报错是”attempt to yield from outside a coroutine”。

  当你yield协程时,倒不会像主协程这样必会报错,但如果你从协程中调入到c层,然后又进入lua层,再yield当前协程,这时将会收到这个报错。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// c
#include <stdio.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
static void traceback(lua_State* L, int n) {
lua_Debug ar;
if (lua_getstack(L, n, &ar)) {
lua_getinfo(L, "Sln", &ar);
if (ar.name) {
printf("\tstack[%d] -> line %d : %s()[%s : line %d]\n", n, ar.currentline, ar.name, ar.short_src, ar.linedefined);
} else {
printf("\tstack[%d] -> line %d : unknown[%s : line %d]\n", n, ar.currentline, ar.short_src, ar.linedefined);
}
traceback(L, n + 1);
}
}
static int errorTraceBack(lua_State *L) {
printf("error stack traceback: %s\n", lua_tostring(L, -1));
traceback(L, 0);
return 0;
}
static int testc(lua_State *L) {
printf("into testc!\n");
lua_pushcfunction(L, errorTraceBack);
lua_getglobal(L, "foo1");
lua_pcall(L, 0, 0, -2);
printf("out testc!\n");
return 0;
}
int main() {
lua_State* L;
int status;
L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, testc);
lua_setglobal(L, "testc");
lua_pushcfunction(L, errorTraceBack);
status = luaL_loadfile(L, "testL1.lua");
if (status) {
printf("loadfile error!(%s)\n", lua_tostring(L, -1));
lua_settop(L, 0);
goto finish;
}
status = lua_pcall(L, 0, 0, -2);
if (status) {
lua_settop(L, 0);
goto finish;
}
finish:
getchar();
lua_close(L);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- lua
function foo1()
print("into foo1!")
coroutine.yield()
print("out foo1!")
end
local co = coroutine.create(
function()
print("coroutine resume!")
testc()
end
)
coroutine.resume(co)

运行结果:

1
2
3
4
5
6
7
8
9
10
coroutine resume!
into testc!
into foo1!
error stack traceback: attempt to yield across metamethod/C-call boundary
stack[0] -> line -1 : unknown[[C] : line -1]
stack[1] -> line -1 : yield()[[C] : line -1]
stack[2] -> line 3 : unknown[testL1.lua : line 1]
stack[3] -> line -1 : testc()[[C] : line -1]
stack[4] -> line 10 : unknown[testL1.lua : line 8]
out testc!

流程:

1
coroutine --> c --> coroutine --> yield ===> 报错

  为什么这种情况下lua会给出这种报错呢?主要是因为在从c函数调回到coroutine中yield时,coroutine当前的堆栈情况会被保存在lua_State中,因此在调用resume时,lua可以恢复yield时的场景,并继续执行下去。但c函数不会因为coroutine的yield被挂起,它会继续执行下去,函数执行完后堆栈就被销毁了,所以无法再次恢复现场。而且因为c函数不会被yield函数挂起,导致c和lua的行为也不一致了,一个被挂起,一个继续执行完,代码逻辑很可能因此出错。

  由此可看出其实这个报错并不是一个严重错误,而是一个保障限制。因为我们的c函数在回调到coroutine后并没有要执行的逻辑,比如例子中testc去掉最后的printf,那么lua_pcall就是最后一个要执行的逻辑,所以c函数并不需要恢复现场。在这种情况下其实lua是不需要报出这个错误的,因为整体逻辑不会收到影响,不过lua为了怕使用者犯错,并没有这么做,毕竟他也不知道你后面有没有要执行的逻辑,除非你告诉他。

  在lua5.2以上版本加入了新的API(lua_callk, lua_pcallk, lua_yieldk),变相解决了c无法被挂起而继续执行后续代码的问题。这些API通过传入一个后续执行函数,让resume后调用它,也就是说你可以把c要后续执行的逻辑都放在这个函数中传给API,这样resume后就会执行这些逻辑了,但如果调用这些API时没有传这个函数,那么逻辑就和lua5.1的一样了,依旧会报错。当然如果我们的c函数并没有要执行的后续逻辑,又不希望收到这个报错,可以向API中传一个空函数,这样就不会报错了。

文章目录