Simple lua_yield in C not resuming correctly from Lua

Viewed 252

I'm just starting to delve into lua coroutines with C and I'm having a problem with what I think should be the simplest example I can come up with.

The C:

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdlib.h>

static int lua_test_yield(lua_State *L) {
        printf("1\n");
        lua_yield(L, 0);
        printf("2\n");
        lua_yield(L, 0);
        printf("3\n");
        lua_yield(L, 0);
        printf("4\n");
        lua_yield(L, 0);
        printf("5\n");
        lua_yield(L, 0);
        return 0;
}

static const struct luaL_Reg mylib[] = {
        {"test_yield", lua_test_yield},

        {NULL, NULL}
        };

// Used when the .so library is loaded from lua
int luaopen_mytest(lua_State *L) {
        luaL_newlib(L, mylib);
        return 1;
}

The Lua

mytest = require 'mytest'

print("Loaded")
mytest_yeild = coroutine.create(function ()
        mytest.test_yield()
end)

for i=1,5 do
        print(coroutine.resume(mytest_yeild))
end 

The result:

$ lua test.lua 
Loaded
1
true
true
false   cannot resume dead coroutine
false   cannot resume dead coroutine
false   cannot resume dead coroutine

I find this really weird. Why does it report a successful resume twice but not print anything and then report a failed resume? What am I missing here? Thanks.

2 Answers

To return to C code after yielding, you need to use lua_yieldk and have it pick up in a separate C function, like this:

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdlib.h>

static int lua_test_yield_6(lua_State *L, int status, lua_KContext ctx) {
        return 0;
}

static int lua_test_yield_5(lua_State *L, int status, lua_KContext ctx) {
        printf("5\n");
        return lua_yieldk(L, 0, 0, lua_test_yield_6);
}

static int lua_test_yield_4(lua_State *L, int status, lua_KContext ctx) {
        printf("4\n");
        return lua_yieldk(L, 0, 0, lua_test_yield_5);
}

static int lua_test_yield_3(lua_State *L, int status, lua_KContext ctx) {
        printf("3\n");
        return lua_yieldk(L, 0, 0, lua_test_yield_4);
}

static int lua_test_yield_2(lua_State *L, int status, lua_KContext ctx) {
        printf("2\n");
        return lua_yieldk(L, 0, 0, lua_test_yield_3);
}

static int lua_test_yield_1(lua_State *L) {
        printf("1\n");
        return lua_yieldk(L, 0, 0, lua_test_yield_2);
}

static const struct luaL_Reg mylib[] = {
        {"test_yield", lua_test_yield_1},

        {NULL, NULL}
        };

// Used when the .so library is loaded from lua
int luaopen_mytest(lua_State *L) {
        luaL_newlib(L, mylib);
        return 1;
}

lua_yield is a C function, and C does not have a mechanism to magically jump back into a function that has halted. lua_yield uses the C standard library longjmp function to arbitrarily jump out of the function that called it. But it can't come back.

So what happens is that your C function yields, exiting the function and returning control to the Lua code that called coroutine.resume. The resume was successful, so true is printed. You then resume the coroutine again, which begins execution at the site in Lua code that called the C function that yielded. That code then exits the coroutine normally. Since the resume was also successful, true is printed again.

But the coroutine is now exhausted and therefore cannot be resumed.

C functions don't have a clean way to be "resumed". Indeed, the Lua 5.1 documentation explicitly states:

This function should only be called as the return expression of a C function, as follows:

return lua_yield (L, nresults);

Lua 5.2 and above have some ability to resume a C function, but only through a continuation API. You cannot write a single C function that works the way a Lua yielding function works.

Related