fix lua_thread_resume_state to not call lua_resume from a CFunction executing in the same state; lua_thread_done is now unprotected...
#include "lua_thread.h"
#include "log.h"
#include <string.h>
#include <assert.h>
#include <lua5.1/lauxlib.h>
void lua_thread_init (struct lua_thread *thread, struct nexus_lua *lua, lua_thread_cb cb_func, void *cb_arg)
{
// zero
memset(thread, 0, sizeof(*thread));
// store
thread->lua = lua;
thread->cb_func = cb_func;
thread->cb_arg = cb_arg;
}
/**
* Thread completed, cleanup and call user callback with given error (NULL for success).
*/
static void lua_thread_done (struct lua_thread *thread, error_t *err)
{
// remove from registry so thread can be GC'd
// XXX: unsafe
lua_pushthread(thread->L);
lua_pushnil(thread->L);
lua_settable(thread->L, LUA_REGISTRYINDEX);
// drop our ref
thread->L = NULL;
// call
thread->cb_func(err, thread->cb_arg);
}
/**
* Execute the lua_resume with zero arguments. If the thread finished, this returns zero, if it yielded, >0. For
* errors, returns -err_t.
*/
static int lua_thread_resume (lua_State *L, struct lua_thread *thread, error_t *err)
{
int ret;
(void) thread;
// invoke it
switch ((ret = lua_resume(L, 0))) {
case LUA_YIELD:
// ok, running, wait
return 1;
case 0:
// done
return 0;
default:
// let caller handle
// XXX: backtrace...
return -nexus_lua_error(L, ret, err);
}
}
struct lua_thread_start_ctx {
struct lua_thread *thread;
const char *chunk;
};
/**
* Create a new thread, set it up, and run the initial resume, returning the boolean result of that
* (true for yielded, false for done).
*/
static int _lua_thread_start (lua_State *L)
{
struct lua_thread_start_ctx *ctx;
lua_State *TL;
error_t err;
int ret;
// read the ctx argument off the stack
if ((ctx = lua_touserdata(L, 1)) == NULL)
return luaL_error(L, "lua_touserdata");
else
lua_pop(L, 1);
// check
assert(!ctx->thread->L);
// create new thread
TL = lua_newthread(L);
// push the lua_thread as the value
lua_pushlightuserdata(L, ctx->thread);
// store L -> lua_thread mapping in the registry
lua_settable(L, LUA_REGISTRYINDEX);
// load the chunk as a lua function into the thread's state
if (nexus_lua_error(TL, luaL_loadstring(TL, ctx->chunk), &err))
goto error;
// initial resume on the loaded chunk
if ((ret = lua_thread_resume(TL, ctx->thread, &err)) < 0)
goto error;
// yielded?
if (ret)
// store
ctx->thread->L = TL;
// pop thread to release for GC
lua_pop(L, 1);
return 0;
error:
// pop thread to release for GC
lua_pop(L, 1);
// drop ref
ctx->thread->L = NULL;
// pass on error
return luaL_error(L, "%s", error_msg(&err));
}
int lua_thread_start (struct lua_thread *thread, const char *chunk, error_t *err)
{
struct lua_thread_start_ctx ctx = { thread, chunk };
// sanity-check
assert(!thread->L);
// use protected mode to setup
if (nexus_lua_error(thread->lua->st, lua_cpcall(thread->lua->st, _lua_thread_start, &ctx), err))
return -ERROR_CODE(err);
// return true if yielded, false if done
return (thread->L != NULL);
}
int lua_thread_yield_state (lua_State *L)
{
return lua_yield(L, 0);
}
static int _lua_thread_resume_state (lua_State *L)
{
struct lua_thread *thread, **thread_ptr;
// read the ctx argument off the stack
if ((thread_ptr = lua_touserdata(L, 1)) == NULL)
return luaL_error(L, "lua_touserdata");
else
lua_pop(L, 1);
// lookup the thread's context
lua_pushthread(L);
lua_gettable(L, LUA_REGISTRYINDEX);
// not registered?
if (lua_isnil(L, -1))
return luaL_error(L, "not a registered lua_thread state");
else if (lua_isboolean(L, -1) && !lua_toboolean(L, -1))
return luaL_error(L, "lua_thread was aborted");
// get it as a pointer
if ((thread = lua_touserdata(L, -1)) == NULL)
return luaL_error(L, "lua_touserdata");
else
lua_pop(L, 1);
// store it
*thread_ptr = thread;
// ok
return 0;
}
void lua_thread_resume_state (lua_State *L)
{
struct lua_thread *thread = NULL;
error_t err;
int ret;
// execute in protected mode
if (nexus_lua_error(L, lua_cpcall(L, _lua_thread_resume_state, &thread), &err))
return log_warn_error(&err, "%p", L);
// handle the resume
if ((ret = lua_thread_resume(L, thread, &err)) < 0)
// notify user
lua_thread_done(thread, &err);
// finished?
else if (ret == 0)
lua_thread_done(thread, NULL);
}
static int _lua_thread_abort (lua_State *L)
{
struct lua_thread *thread;
// get context arg
if ((thread = lua_touserdata(L, -1)) == NULL)
luaL_error(L, "lua_thread_resume_state: lua_touserdata");
else
lua_pop(L, 1);
// no mechanism to actually abort the underlying wait yet
(void) thread;
// invalidate in registry for later lua_thread_resume_state
lua_pushthread(L);
lua_pushboolean(L, false);
lua_settable(L, LUA_REGISTRYINDEX);
// not much else we can do
return 0;
}
void lua_thread_abort (struct lua_thread *thread)
{
error_t err;
if (!thread->L)
// no thread executing
return;
// use protected mode
if (nexus_lua_error(thread->L, lua_cpcall(thread->L, _lua_thread_abort, thread), &err))
log_warn_error(&err, "_lua_thread_abort");
// unset
thread->L = NULL;
}