]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-lua: Add new thread/close thread dlua wrappers
authorAki Tuomi <aki.tuomi@open-xchange.com>
Fri, 5 Feb 2021 14:47:53 +0000 (16:47 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Fri, 19 Mar 2021 11:37:42 +0000 (11:37 +0000)
Code rewritten to use lua registry, warn about unclosed threads, and moved
to a separate file by Josef 'Jeff' Sipek <jeff.sipek@open-xchange.com>

src/lib-lua/Makefile.am
src/lib-lua/dlua-script-private.h
src/lib-lua/dlua-script.c
src/lib-lua/dlua-thread.c [new file with mode: 0644]

index a76e9b8e90d1013d4c393b3fac12811d14aa82b8..48c1f9bb69f552fc44492176b7efa205c25bc94e 100644 (file)
@@ -8,7 +8,8 @@ libdovecot_lua_la_SOURCES = \
        dlua-script.c \
        dlua-dovecot.c \
        dlua-compat.c \
-       dlua-table.c
+       dlua-table.c \
+       dlua-thread.c
 # Note: the only things this lib should depend on are libdovecot and lua.
 libdovecot_lua_la_DEPENDENCIES = ../lib-dovecot/libdovecot.la
 libdovecot_lua_la_LIBADD = ../lib-dovecot/libdovecot.la $(LUA_LIBS)
index 5e1809ac2532abba0bc3e2d661f7af351ec49910..ef378caec429d9bde713c0faf9728b79bac38660 100644 (file)
@@ -39,7 +39,7 @@ struct dlua_script {
        struct dlua_script *prev,*next;
        pool_t pool;
 
-       lua_State *L;
+       lua_State *L; /* base lua context */
 
        struct event *event;
        const char *filename;
@@ -169,4 +169,14 @@ int dlua_table_get_by_thread(lua_State *L, int idx, int type);
 /* dumps current stack as i_debug lines */
 void dlua_dump_stack(lua_State *L);
 
+/* Create new thread and keep track of it. */
+lua_State *dlua_script_new_thread(struct dlua_script *script);
+
+/* Close thread. */
+void dlua_script_close_thread(struct dlua_script *script, lua_State **_L);
+
+/* initialize/free script's thread table */
+void dlua_init_thread_table(struct dlua_script *script);
+void dlua_free_thread_table(struct dlua_script *script);
+
 #endif
index 8753c2f8fc2a9133a2d969d90616c2e5ac246460..79350611254b5af30f88f737b620b5c416543616 100644 (file)
@@ -190,6 +190,8 @@ static struct dlua_script *dlua_create_script(const char *name,
        script->event = event_create(event_parent);
        event_add_category(script->event, &event_category_lua);
 
+       dlua_init_thread_table(script);
+
        return script;
 }
 
@@ -304,7 +306,12 @@ static void dlua_script_destroy(struct dlua_script *script)
 {
        dlua_call_deinit_function(script);
 
+       /* close all threads */
+       dlua_free_thread_table(script);
+
+       /* close base lua */
        lua_close(script->L);
+
        /* remove from list */
        DLLIST_REMOVE(&dlua_scripts, script);
 
diff --git a/src/lib-lua/dlua-thread.c b/src/lib-lua/dlua-thread.c
new file mode 100644 (file)
index 0000000..84ac488
--- /dev/null
@@ -0,0 +1,125 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "dlua-script-private.h"
+
+/*
+ * dlua support for threads
+ *
+ * The following code keeps a table in (global) registry.  This table is
+ * indexed by the thread objects, each mapping to another table.  That is:
+ *
+ *   registry[thread] = {}  -- threads table
+ *
+ * This acts as a reference to the thread object, preventing it from being
+ * garbage collected.
+ *
+ * The table is allocated during struct dlua_script's creation and is freed
+ * during the scripts destruction.  Any lua threads created using
+ * dlua_script_new_thread() will automatically get added to this table.
+ */
+
+/* the registry entry with a table with all the lua threads */
+#define LUA_THREAD_REGISTRY_KEY "DLUA_THREADS"
+
+void dlua_init_thread_table(struct dlua_script *script)
+{
+       lua_newtable(script->L);
+       lua_setfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+
+       /*
+        * Note that we are *not* adding the main lua state since it is not
+        * a thread.
+        */
+}
+
+static void warn_about_leaked_threads(struct dlua_script *script)
+{
+       lua_State *L = script->L;
+
+       lua_getfield(L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+
+       i_assert(lua_type(L, -1) == LUA_TTABLE);
+
+       lua_pushnil(L);
+       while (lua_next(L, -2) != 0) {
+               /* stack: table, thread, per-thread table */
+
+               /* check the key */
+               if (lua_type(L, -2) != LUA_TTHREAD) {
+                       e_error(script->event, "Unexpected %s key in thread table",
+                               lua_typename(L, lua_type(L, -2)));
+               } else {
+                       e_error(script->event, "Lua thread %p leaked", lua_tothread(L, -2));
+               }
+
+               /* check the value */
+               if (lua_type(L, -1) != LUA_TTABLE) {
+                       e_error(script->event, "Unexpected %s value in thread table",
+                               lua_typename(L, lua_type(L, -1)));
+               }
+
+               /* pop the value for lua_next() */
+               lua_pop(L, 1);
+       }
+
+       lua_pop(L, 1);
+}
+
+void dlua_free_thread_table(struct dlua_script *script)
+{
+       /* all threads should have been closed by now */
+       warn_about_leaked_threads(script);
+
+       /* set the thread table to nil - letting GC clean everything up */
+       lua_pushnil(script->L);
+       lua_setfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+}
+
+lua_State *dlua_script_new_thread(struct dlua_script *script)
+{
+       lua_State *thread;
+
+       /* get the threads table */
+       lua_getfield(script->L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+
+       /* allocate a new thread */
+       thread = lua_newthread(script->L);
+       i_assert(thread != NULL);
+
+       /* allocate new per-thread table */
+       lua_newtable(script->L);
+
+       /* stack: threads-table, thread, per-thread-table (top) */
+
+       /* threads-table[thread] = per-thread-table */
+       lua_settable(script->L, -3);
+
+       return thread;
+}
+
+void dlua_script_close_thread(struct dlua_script *script, lua_State **_L)
+{
+       if (*_L == NULL)
+               return;
+
+       /* get the threads table */
+       lua_getfield(*_L, LUA_REGISTRYINDEX, LUA_THREAD_REGISTRY_KEY);
+
+       /* push the thread to destroy */
+       i_assert(lua_pushthread(*_L) != 1);
+
+       lua_pushnil(*_L);
+
+       /* stack: threads-table, thread, nil (top) */
+
+       /*
+        * threads-table[thread] = nil
+        *
+        * This assignment removes the reference to the thread saving it
+        * from GC.
+        */
+       lua_settable(*_L, -3);
+
+       *_L = NULL;
+}