implement lua_threads, nexus:sleep test func, and basic support in console/lua_console... doesn't actually work yet
--- 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