# HG changeset patch # User Tero Marttila # Date 1242849185 -10800 # Node ID ffdf53fd03370c5f52f4d43da389a26d38b3d29c # Parent 210c43e6c088d7347b4d324f0bf6dd74d75ada02 implement lua_threads, nexus:sleep test func, and basic support in console/lua_console... doesn't actually work yet diff -r 210c43e6c088 -r ffdf53fd0337 src/CMakeLists.txt --- 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) diff -r 210c43e6c088 -r ffdf53fd0337 src/console.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) diff -r 210c43e6c088 -r ffdf53fd0337 src/console.h --- 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); diff -r 210c43e6c088 -r ffdf53fd0337 src/lua_console.c --- 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 -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; diff -r 210c43e6c088 -r ffdf53fd0337 src/lua_console.h --- 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 @@ -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 diff -r 210c43e6c088 -r ffdf53fd0337 src/lua_objs.c --- 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 @@ -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 } }; diff -r 210c43e6c088 -r ffdf53fd0337 src/lua_thread.c --- /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 +#include + +#include + + +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); +} + diff -r 210c43e6c088 -r ffdf53fd0337 src/lua_thread.h --- /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 + +/* + * 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