src/lua_thread.c
author Tero Marttila <terom@fixme.fi>
Sat, 23 May 2009 00:33:23 +0300
changeset 215 85863b89e38b
parent 214 0d5d46ab49d5
permissions -rw-r--r--
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;
}