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