implement lua_threads, nexus:sleep test func, and basic support in console/lua_console... doesn't actually work yet lua-threads
authorTero Marttila <terom@fixme.fi>
Wed, 20 May 2009 22:53:05 +0300
branchlua-threads
changeset 203 ffdf53fd0337
parent 202 210c43e6c088
child 204 7dfc3d7bd92b
implement lua_threads, nexus:sleep test func, and basic support in console/lua_console... doesn't actually work yet
src/CMakeLists.txt
src/console.c
src/console.h
src/lua_console.c
src/lua_console.h
src/lua_objs.c
src/lua_thread.c
src/lua_thread.h
--- a/src/CMakeLists.txt	Wed May 20 22:52:01 2009 +0300
+++ b/src/CMakeLists.txt	Wed May 20 22:53:05 2009 +0300
@@ -14,7 +14,7 @@
 set (IO_SOURCES transport.c service.c transport_fd.c sock.c resolve.c tcp.c tcp_transport.c tcp_client.c tcp_server.c ssl.c ssl_client.c fifo.c)
 set (PROTO_SOURCES line_proto.c msg_proto.c)
 set (IRC_SOURCES irc_line.c irc_conn.c irc_net.c irc_chan.c chain.c irc_cmd.c irc_proto.c irc_client.c irc_user.c irc_queue.c irc_net_connect.c)
-set (LUA_SOURCES nexus_lua.c lua_objs.c lua_config.c lua_irc.c lua_func.c lua_type.c)
+set (LUA_SOURCES nexus_lua.c lua_objs.c lua_config.c lua_irc.c lua_func.c lua_type.c lua_thread.c)
 set (CONSOLE_SOURCES console.c lua_console.c)
 
 set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${IO_SOURCES} ${PROTO_SOURCES} ${IRC_SOURCES} ${LUA_SOURCES} ${CONSOLE_SOURCES} signals.c module.c config.c)
--- a/src/console.c	Wed May 20 22:52:01 2009 +0300
+++ b/src/console.c	Wed May 20 22:53:05 2009 +0300
@@ -32,8 +32,11 @@
     /** Set as log output function? */
     bool log_output;
 
-    /** In the middle of input? */
+    /** Prompt/input displayed? */
     bool have_input;
+
+    /** Waiting for line to be processed */
+    bool waiting;
 };
 
 /** The global console state */
@@ -50,9 +53,11 @@
     (void) what;
 
     // update state
+    // XXX: needed?
     console->have_input = true;
 
     // tell readline to process it
+    // XXX: don't if console->waiting
     rl_callback_read_char();
 }
 
@@ -62,6 +67,7 @@
 static void console_line (char *line)
 {
     struct console *console = &_console;
+    enum console_line_status status = CONSOLE_CONTINUE;
     
     // special-case EOF
     if (!line) {
@@ -75,12 +81,12 @@
         return;
     }
 
-    // update state
+    // update state for console_print during processing
     console->have_input = false;
 
     // invoke the console callback
     if (console->callbacks && console->callbacks->on_line)
-        console->callbacks->on_line(line, console->cb_arg);
+        status = console->callbacks->on_line(line, console->cb_arg);
     
     // add to history mechanism
     add_history(line);
@@ -88,8 +94,22 @@
     // release the line
     free(line);
     
-    // update state, as the prompt will be displayed again
-    console->have_input = true;
+    switch (status) {
+        case CONSOLE_CONTINUE:
+            // update state, as the prompt will be displayed again
+            console->have_input = true;
+
+            break;
+
+        case CONSOLE_WAIT:
+            // remove the readline stuff to stop it from displaying the prompt
+            rl_callback_handler_remove();
+
+            // update state to ignore input
+            console->waiting = true;
+
+            break;
+    }
 }
 
 static void on_sigint (int sig)
@@ -160,6 +180,20 @@
     console->cb_arg = cb_arg;
 }
 
+void console_continue (struct console *console)
+{
+    assert(console->waiting);
+
+    // re-enable input
+    console->waiting = false;
+
+    // the prompt will be displayed again
+    console->have_input = true;
+
+    // re-setup readline with prompt
+    rl_callback_handler_install(console->config.prompt, console_line);
+}
+
 err_t console_print (struct console *console, const char *line)
 {
     if (console->have_input)
--- a/src/console.h	Wed May 20 22:52:01 2009 +0300
+++ b/src/console.h	Wed May 20 22:53:05 2009 +0300
@@ -21,13 +21,32 @@
 struct console;
 
 /**
+ * Return codes for console_callbacks::on_line
+ */
+enum console_line_status {
+    /** 
+     * OK, line was processed, display prompt for next input line 
+     */
+    CONSOLE_CONTINUE,
+
+    /**
+     * Line is still executing, do not prompt for next line .
+     *
+     * Call console_continue() once the line was handled.
+     */
+    CONSOLE_WAIT,
+};
+
+/**
  * Callbacks for event-based actions
  */
 struct console_callbacks {
     /**
-     * A line was read from the console. 
+     * A line was read from the console.
+     *
+     * The return code defines how execution continues.
      */
-    void (*on_line) (const char *line, void *arg);
+    enum console_line_status (*on_line) (const char *line, void *arg);
 
     /**
      * EOF was read on the console.
@@ -64,6 +83,11 @@
 void console_set_callbacks (struct console *console, const struct console_callbacks *callbacks, void *cb_arg);
 
 /**
+ * Continue reading input after a CONSOLE_WAIT return code from console_callbacks::on_line.
+ */
+void console_continue (struct console *console);
+
+/**
  * Output a full line (without included newline) on the console, trying not to interfere with the input too much.
  */
 err_t console_print (struct console *console, const char *line);
--- a/src/lua_console.c	Wed May 20 22:52:01 2009 +0300
+++ b/src/lua_console.c	Wed May 20 22:53:05 2009 +0300
@@ -6,21 +6,56 @@
 
 #include <lua5.1/lauxlib.h>
 
-static void lua_console_on_line (const char *line, void *arg)
+struct lua_console {
+    /** The lowlevel line-based console */
+    struct console *console;
+
+    /** Our lua state */
+    struct nexus_lua *lua;
+
+    /** Coroutine state */
+    struct lua_thread thread;
+};
+
+/**
+ * Line finished execution
+ */
+static void lua_console_on_thread (struct lua_thread *thread, const error_t *err, void *arg)
 {
     struct lua_console *lc = arg;
-    struct error_info err;
+
+    if (err)
+        // display error message
+        console_print(lc->console, error_msg(err));
+
+    // XXX: display return value?
+    
+    // un-wait console
+    console_continue(lc->console);
+}
+
+/**
+ * Got a line to exec from the console
+ */
+static enum console_line_status lua_console_on_line (const char *line, void *arg)
+{
+    struct lua_console *lc = arg;
+    error_t err;
 
     // ignore empty lines and EOF
     if (!line || !(*line))
-        return;
+        return CONSOLE_CONTINUE;
     
-    // eval it
-    if (nexus_lua_eval(lc->lua, line, &err))
+    // exec it in our thread
+    if (lua_thread_start(&lc->thread, line, &err)) {
+        // display error message
         console_print(lc->console, error_msg(&err));
 
-    // XXX: display return value?
-     
+        return CONSOLE_CONTINUE;
+    } 
+
+    // wait for it to execute
+    return CONSOLE_WAIT;
 }
 
 static void lua_console_on_eof (void *arg)
@@ -51,6 +86,9 @@
     // set our console callbacks
     console_set_callbacks(console, &_console_callbacks, lc);
 
+    // init thread
+    lua_thread_init(&lc->thread, lua, lua_console_on_thread, lc);
+
     // ok
     *lc_ptr = lc;
 
--- a/src/lua_console.h	Wed May 20 22:52:01 2009 +0300
+++ b/src/lua_console.h	Wed May 20 22:53:05 2009 +0300
@@ -6,7 +6,7 @@
  *
  * An interactive lua console
  */
-#include "nexus_lua.h"
+#include "lua_thread.h"
 #include "console.h"
 
 #include <lua5.1/lua.h>
@@ -14,13 +14,7 @@
 /**
  * The lua console state
  */
-struct lua_console {
-    /** The lowlevel line-based console */
-    struct console *console;
-
-    /** Our lua state */
-    struct nexus_lua *lua;
-};
+struct lua_console;
 
 /**
  * Create a new lua console based on the given low-level console, operating on the given nexus
--- a/src/lua_objs.c	Wed May 20 22:52:01 2009 +0300
+++ b/src/lua_objs.c	Wed May 20 22:53:05 2009 +0300
@@ -1,5 +1,6 @@
 #include "lua_objs.h"
 #include "lua_irc.h"
+#include "lua_func.h"
 #include "log.h"
 
 #include <stdlib.h>
@@ -338,10 +339,47 @@
     return 0;
 }
 
+static struct lua_func lua_nexus_sleep_func = LUA_FUNC(&lua_nexus_type, "sleep",
+        "Schedules itself to resume after the given delay (in seconds) and yields",
+
+        LUA_FUNC_ARG_INT("tv_sec",  LUA_ARG_REQUIRED)
+    );
+
+static void lua_nexus_sleep_wakeup (evutil_socket_t fd, short what, void *arg)
+{
+    lua_State *L = arg;
+
+    (void) fd;
+    (void) what;
+
+    // resume the thread that called lua_nexus_sleep
+    lua_thread_resume_state(L);
+}
+
+static int lua_nexus_sleep (lua_State *L)
+{
+    struct lua_nexus *lua_nexus;
+    long tv_sec;
+
+    // parse args
+    lua_args_parse(L, &lua_nexus_sleep_func, (void *) &lua_nexus, &tv_sec);
+
+    // build tv
+    struct timeval tv = { tv_sec, 0 };
+    
+    // schedule wakeup
+    // use a pure-timeout event
+    if (event_base_once(lua_nexus->nexus->ev_base, -1, 0, lua_nexus_sleep_wakeup, L, &tv))
+        return luaL_error(L, "event_base_once");
+
+    // yield
+    return lua_yield(L, 0);
+}
 
 static struct lua_method lua_nexus_methods[] = LUA_METHODS(
-        LUA_METHOD("shutdown",      lua_nexus_shutdown,     NULL    ),
-        LUA_METHOD("load_config",   lua_nexus_load_config,  NULL    )
+        LUA_METHOD("shutdown",      lua_nexus_shutdown,     NULL                    ),
+        LUA_METHOD("load_config",   lua_nexus_load_config,  NULL                    ),
+        LUA_METHOD("sleep",         lua_nexus_sleep,        &lua_nexus_sleep_func   )
     );
 
 /**
@@ -389,8 +427,8 @@
 }
 
 static const struct luaL_Reg lua_global_functions[] = {
-    {   "log_level",    &lua_log_level              },
-    {   "log",          &lua_log                    },
+    {   "log_level",    lua_log_level               },
+    {   "log",          lua_log                     },
     {   NULL,           NULL                        }
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lua_thread.c	Wed May 20 22:53:05 2009 +0300
@@ -0,0 +1,171 @@
+#include "lua_thread.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 (lua_State *L, struct lua_thread *thread, error_t *err)
+{
+    assert(L == thread->L);
+
+    // remove from registry so thread can be GC'd
+    lua_pushthread(L);
+    lua_pushnil(L);
+    lua_settable(L, LUA_REGISTRYINDEX);
+
+    // drop our ref
+    thread->L = NULL;
+
+    // call
+    thread->cb_func(thread, err, thread->cb_arg);
+}
+
+/**
+ * Execute the lua_resume with zero arguments, invoking the callback on succesful completion, and returning any
+ * error.
+ */
+static err_t lua_thread_resume (lua_State *L, struct lua_thread *thread, error_t *err)
+{
+    int ret;
+
+    // invoke it
+    switch ((ret = lua_resume(L, 0))) {
+        case LUA_YIELD:
+            // ok, running, wait
+            return SUCCESS;
+
+        case 0:
+            // done, notify user
+            lua_thread_done(L, thread, NULL);
+
+            return SUCCESS;
+
+        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;
+};
+
+/**
+ * Setup the given newthread state, context, and load/run the given chunk
+ */
+static int lua_thread_setup (lua_State *L)
+{
+    struct lua_thread_start_ctx *ctx;
+    error_t err;
+
+    // read the ctx argument off the stack
+    if ((ctx = lua_touserdata(L, 1)) == NULL)
+        luaL_error(L, "lua_touserdata");
+
+    // push the thread state as the key
+    lua_pushthread(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);
+
+    // clean up the stack
+    lua_pop(L, 1);
+    
+    // load the chunk as a lua function into the thread's state
+    if (nexus_lua_error(L, luaL_loadstring(L, ctx->chunk), &err))
+        luaL_error(L, "%s", error_msg(&err));
+    
+    // initial resume on the loaded chunk
+    if (lua_thread_resume(L, ctx->thread, &err))
+        luaL_error(L, "%s", error_msg(&err));
+
+    // ok
+    return 0;
+}
+
+static int _lua_thread_start (lua_State *L)
+{
+    struct lua_thread_start_ctx *ctx;
+    lua_State *TL;
+    error_t err;
+
+    // read the ctx argument off the stack
+    if ((ctx = lua_touserdata(L, 1)) == NULL)
+        luaL_error(L, "lua_touserdata");
+    
+    // create new thread
+    TL = lua_newthread(L);
+
+    // create thread and do setup on it
+    if (nexus_lua_error(TL, lua_cpcall(TL, lua_thread_setup, ctx), &err)) {
+        // cleanup stack (ctx, TL, error message)
+        lua_pop(L, 3);
+
+        // pass on error
+        luaL_error(L, "lua_thread_setup: %s", error_msg(&err));
+    }
+        
+    // drop the thread for GC, we don't need it, and also our ctx arg
+    lua_pop(L, 2);
+    
+    // store
+    ctx->thread->L = TL;
+
+    return 0;
+}
+
+err_t 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);
+
+    // ok
+    return SUCCESS;
+}
+
+void lua_thread_resume_state (lua_State *L)
+{
+    struct lua_thread *thread;
+    error_t err;
+
+    // lookup the thread's context
+    lua_pushthread(L);
+    lua_gettable(L, LUA_REGISTRYINDEX);
+
+    // get it as a pointer
+    if ((thread = lua_touserdata(L, -1)) == NULL)
+        luaL_error(L, "lua_thread_resume_state: lua_touserdata");
+
+    // handle the resume
+    if (lua_thread_resume(L, thread, &err))
+        // notify user
+        lua_thread_done(thread->L, thread, &err);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lua_thread.h	Wed May 20 22:53:05 2009 +0300
@@ -0,0 +1,67 @@
+#ifndef LUA_THREAD_H
+#define LUA_THREAD_H
+
+/**
+ * @file
+ *
+ * Running code as lua coroutines for a nexus_lua
+ */
+#include "nexus_lua.h"
+#include <lua5.1/lua.h>
+
+/*
+ * Forward-declare
+ */
+struct lua_thread;
+
+/**
+ * Callback for async execution completion.
+ *
+ * Called with err == NULL for success, error code otherwise.
+ */
+typedef void (*lua_thread_cb) (struct lua_thread *thread, const error_t *err, void *arg);
+
+/**
+ * A thread of execution
+ */
+struct lua_thread {
+    /** The lua_nexus environment we're running under */
+    struct nexus_lua *lua;
+
+    /** Callback */
+    lua_thread_cb cb_func;
+
+    /** Callback context argument */
+    void *cb_arg;
+
+    /** Real lua thread state */
+    lua_State *L;
+};
+
+/**
+ * Initialize the given lua_thread state for execution.
+ *
+ * You only need to call this once for each lua_thread that you use.
+ */
+void lua_thread_init (struct lua_thread *thread, struct nexus_lua *lua, lua_thread_cb cb_func, void *cb_arg);
+
+/**
+ * Execute the given chunk in a thread context.
+ *
+ * This should be called from unprotected mode, and the thread must currently not be active.
+ *
+ * This will load the chunk, create a new lua thread state off the lua_nexus, and then do the initial 'lua_resume' call
+ * on the loaded chunk.
+ *
+ * If this process raises an error, it will be returned directly. Otherwise, the thread's callback function will
+ * eventually be called once the thread either errors out or finishes execution.
+ */
+err_t lua_thread_start (struct lua_thread *thread, const char *chunk, error_t *err);
+
+/**
+ * Protected-mode function to resume execution of the given lua_State, which must be a lua_State created with
+ * lua_thread_start.
+ */
+void lua_thread_resume_state (lua_State *L);
+
+#endif