diff --git a/daemon/README.rst b/daemon/README.rst index 943b87c13e260367a47e4bb9ae840fbf9c6c5332..dff2e49417f1b93b58bd624d5def87c42d29f808 100644 --- a/daemon/README.rst +++ b/daemon/README.rst @@ -153,20 +153,56 @@ to download cache from parent, to avoid cold-cache start. Events and services ^^^^^^^^^^^^^^^^^^^ -The Lua supports a concept called closures, this is extremely useful for scripting actions upon various events. +The Lua supports a concept called closures_, this is extremely useful for scripting actions upon various events, +say for example - prune the cache within minute after loading, publish statistics each 5 minutes and so on. +Here's an example of an anonymous function with :func:`event.recurrent()`: -.. note:: Work in progress, come back later! +.. code-block:: lua + + -- every 5 minutes + event.recurrent(5 * minute, function() + cachectl.prune() + end) + +Note that each scheduled event is identified by a number valid for the duration of the event, +you may cancel it at any time. You can do this with anonymous functions, if you accept the event +as a parameter, but it's not very useful as you don't have any *non-global* way to keep persistent variables. + +.. code-block:: lua + + -- make a closure, encapsulating counter + function pruner() + local i = 0 + -- pruning function + return function(e) + cachectl.prune() + -- cancel event on 5th attempt + i = i + 1 + if i == 5 then + event.cancel(e) + fi + end + end + + -- make recurrent event that will cancel after 5 times + event.recurrent(5 * minute, pruner()) -* Timers and events * File watchers * Data I/O +.. note:: Work in progress, come back later! + +.. _closures: http://www.lua.org/pil/6.1.html Configuration reference ----------------------- This is a reference for variables and functions available to both configuration file and CLI. +.. contents:: + :depth: 1 + :local: + Environment ^^^^^^^^^^^ @@ -319,6 +355,56 @@ daemons or manipulated from other processes, making for example synchronised loa Close the cache. + +Timers and events +^^^^^^^^^^^^^^^^^ + +The timer represents exactly the thing described in the examples - it allows you to execute closures after specified time, +or event recurrent events. Time is always described in miliseconds, but there are convenient variables that you can use - +``sec, minute, hour``. For example, ``5 * hour`` represents five hours, or 5*60*60*100 milliseconds. + +.. function:: event.after(time, function) + + :return: event id + + Execute function after the specified time has passed. + The first parameter of the callback is the event itself. + + Example: + + .. code-block:: lua + + event.after(1 * minute, function() print('Hi!') end) + +.. function:: event.recurrent(interval, function) + + :return: event id + + Similar to :func:`event.after()`, periodically execute function after ``interval`` passes. + + Example: + + .. code-block:: lua + + msg_count = 0 + event.recurrent(5 * sec, function(e) + msg_count = msg_count + 1 + print('Hi #'..msg_count) + end) + +.. function:: event.cancel(event_id) + + Cancel running event, it has no effect on already canceled events. + New events may reuse the event_id, so the behaviour is undefined if the function + is called after another event is started. + + Example: + + .. code-block:: lua + + e = event.after(1 * minute, function() print('Hi!') end) + event.cancel(e) + .. _`JSON-encoded`: http://json.org/example .. _`Learn Lua in 15 minutes`: http://tylerneylon.com/a/learn-lua/ .. _`PowerDNS Recursor`: https://doc.powerdns.com/md/recursor/scripting/ diff --git a/daemon/bindings.c b/daemon/bindings.c index dc677b3e4421786ebf9b11e17d4973043f568c40..d685d1fe7ad651d0272eba5ba4ab1ff39be4e9dd 100644 --- a/daemon/bindings.c +++ b/daemon/bindings.c @@ -18,6 +18,7 @@ #include "lib/cache.h" #include "daemon/bindings.h" +#include "daemon/worker.h" /** @internal Prefix error with file:line */ static int format_error(lua_State* L, const char *err) @@ -355,3 +356,119 @@ int lib_cache(lua_State *L) register_lib(L, "cache", lib); return 1; } + +static void event_free(uv_timer_t *timer) +{ + struct worker_ctx *worker = timer->loop->data; + lua_State *L = worker->engine->L; + int ref = (intptr_t) timer->data; + luaL_unref(L, LUA_REGISTRYINDEX, ref); + free(timer); +} + +static void event_callback(uv_timer_t *timer) +{ + struct worker_ctx *worker = timer->loop->data; + lua_State *L = worker->engine->L; + + /* Retrieve callback and execute */ + lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t) timer->data); + lua_rawgeti(L, -1, 1); + lua_pushinteger(L, (intptr_t) timer->data); + engine_pcall(L, 1); + + /* Free callback if not recurrent */ + if (uv_timer_get_repeat(timer) == 0) { + uv_close((uv_handle_t *)timer, (uv_close_cb) event_free); + } +} + +static int event_sched(lua_State *L, unsigned timeout, unsigned repeat) +{ + uv_timer_t *timer = malloc(sizeof(*timer)); + if (!timer) { + format_error(L, "out of memory"); + lua_error(L); + } + + /* Start timer with the reference */ + uv_loop_t *loop = uv_default_loop(); + uv_timer_init(loop, timer); + int ret = uv_timer_start(timer, event_callback, timeout, repeat); + if (ret != 0) { + free(timer); + format_error(L, "couldn't start the event"); + lua_error(L); + } + + /* Save callback and timer in registry */ + lua_newtable(L); + lua_pushvalue(L, 2); + lua_rawseti(L, -2, 1); + lua_pushlightuserdata(L, timer); + lua_rawseti(L, -2, 2); + int ref = luaL_ref(L, LUA_REGISTRYINDEX); + + /* Save reference to the timer */ + timer->data = (void *) (intptr_t)ref; + lua_pushinteger(L, ref); + return 1; +} + +static int event_after(lua_State *L) +{ + /* Check parameters */ + int n = lua_gettop(L); + if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) { + format_error(L, "expected 'after(number timeout, function)'"); + lua_error(L); + } + + return event_sched(L, lua_tonumber(L, 1), 0); +} + +static int event_recurrent(lua_State *L) +{ + /* Check parameters */ + int n = lua_gettop(L); + if (n < 2 || !lua_isnumber(L, 1) || !lua_isfunction(L, 2)) { + format_error(L, "expected 'recurrent(number interval, function)'"); + lua_error(L); + } + return event_sched(L, 0, lua_tonumber(L, 1)); +} + +static int event_cancel(lua_State *L) +{ + int n = lua_gettop(L); + if (n < 1 || !lua_isnumber(L, 1)) { + format_error(L, "expected 'cancel(number event)'"); + lua_error(L); + } + + /* Fetch event if it exists */ + lua_rawgeti(L, LUA_REGISTRYINDEX, lua_tointeger(L, 1)); + if (!lua_istable(L, -1)) { + format_error(L, "event not exists"); + lua_error(L); + } + + /* Close the timer */ + lua_rawgeti(L, -1, 2); + uv_handle_t *timer = lua_touserdata(L, -1); + uv_close(timer, (uv_close_cb) event_free); + return 0; +} + +int lib_event(lua_State *L) +{ + static const luaL_Reg lib[] = { + { "after", event_after }, + { "recurrent", event_recurrent }, + { "cancel", event_cancel }, + { NULL, NULL } + }; + + register_lib(L, "event", lib); + return 1; +} diff --git a/daemon/bindings.h b/daemon/bindings.h index c1ad1a286aff3fc64c580becc4afd8e72bbc10e6..b6e0b1d1d6f13a789d2400021f23a8b13aa621c7 100644 --- a/daemon/bindings.h +++ b/daemon/bindings.h @@ -44,4 +44,11 @@ int lib_net(lua_State *L); * @param L scriptable * @return number of packages to load */ -int lib_cache(lua_State *L); \ No newline at end of file +int lib_cache(lua_State *L); + +/** + * Load 'event' package. + * @param L scriptable + * @return number of packages to load + */ +int lib_event(lua_State *L); \ No newline at end of file diff --git a/daemon/main.c b/daemon/main.c index f3f4fe547d22a06adccabd28b095baf6be415f5f..24d45963f0eec37ad6936906674fcb6758a25ba5 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -141,6 +141,7 @@ int main(int argc, char **argv) engine_lualib(&engine, "modules", lib_modules); engine_lualib(&engine, "net", lib_net); engine_lualib(&engine, "cache", lib_cache); + engine_lualib(&engine, "event", lib_event); /* Create main worker. */ struct worker_ctx worker = {