summaryrefslogtreecommitdiffstats
path: root/src/lib-lua/dlua-thread.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-lua/dlua-thread.c276
1 files changed, 276 insertions, 0 deletions
diff --git a/src/lib-lua/dlua-thread.c b/src/lib-lua/dlua-thread.c
new file mode 100644
index 0000000..a0441f8
--- /dev/null
+++ b/src/lib-lua/dlua-thread.c
@@ -0,0 +1,276 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "dlua-script-private.h"
+
+/*
+ * dlua support for threads & thread local storage (TLS)
+ *
+ * The following code keeps a table in (global) registry. This table is
+ * indexed by the thread objects, each mapping to another table used to hold
+ * thread local storage. That is:
+ *
+ * registry[thread] = {} -- threads table
+ * registry[thread]["foo"] = ... -- TLS value for "foo"
+ *
+ * This serves two purposes:
+ *
+ * (1) It provides TLS.
+ * (2) It 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"
+
+static void warn_about_tls_leaks(struct dlua_script *script, lua_State *L);
+static void get_tls_table(lua_State *L);
+
+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. This implies that it will not have any TLS.
+ */
+}
+
+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)));
+ } else {
+ warn_about_tls_leaks(script, L);
+ }
+
+ /* 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 TLS table */
+ lua_newtable(script->L);
+
+ /* stack: threads-table, thread, TLS-table (top) */
+
+ /* threads-table[thread] = TLS-table */
+ lua_settable(script->L, -3);
+
+ /* pop threads table */
+ lua_pop(script->L, 1);
+
+ return thread;
+}
+
+static void log_tls_leak(struct dlua_script *script, lua_State *L, bool full)
+{
+ const char *name = NULL;
+
+ /* stack: TLS key, TLS value (top) */
+
+ if (full) {
+ lua_getmetatable(L, -1);
+
+ if (dlua_table_get_string_by_str(L, -1, "__name", &name) < 0)
+ name = NULL;
+
+ lua_pop(L, 1); /* pop the metatable */
+ }
+
+ e_error(script->event, "Lua TLS data in %p thread leaked: key '%s', "
+ "value %s %p (%s)", L, lua_tostring(L, -2),
+ full ? "userdata" : "lightuserdata",
+ lua_touserdata(L, -1), (name != NULL) ? name : "<no name>");
+}
+
+static void warn_about_tls_leaks(struct dlua_script *script, lua_State *L)
+{
+ i_assert(lua_type(L, -1) == LUA_TTABLE);
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+ /* stack: table, key, value (top) */
+
+ switch (lua_type(L, -1)) {
+ case LUA_TNIL:
+ case LUA_TNUMBER:
+ case LUA_TBOOLEAN:
+ case LUA_TSTRING:
+ case LUA_TFUNCTION:
+ case LUA_TTHREAD:
+ /* these are trivially freed by the Lua GC */
+ break;
+ case LUA_TTABLE:
+ /* recurse into the table */
+ warn_about_tls_leaks(script, L);
+ break;
+ case LUA_TUSERDATA:
+ log_tls_leak(script, L, TRUE);
+ break;
+ case LUA_TLIGHTUSERDATA:
+ log_tls_leak(script, L, FALSE);
+ break;
+ }
+
+ /* pop the value for lua_next() */
+ lua_pop(L, 1);
+ }
+}
+
+void dlua_script_close_thread(struct dlua_script *script, lua_State **_L)
+{
+ if (*_L == NULL)
+ return;
+
+ /* log any TLS userdata leaks */
+ get_tls_table(*_L);
+ warn_about_tls_leaks(script, *_L);
+ lua_pop(*_L, 1);
+
+ /* 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 (1) frees all TLS for the thread, and (2) removes
+ * the reference to the thread saving it from GC.
+ */
+ lua_settable(*_L, -3);
+
+ /* pop threads table */
+ lua_pop(*_L, 1);
+
+ *_L = NULL;
+}
+
+/* get the current thread's TLS table */
+static void get_tls_table(lua_State *L)
+{
+ int ret;
+
+ /* get the threads table */
+ ret = dlua_table_get_by_str(L, LUA_REGISTRYINDEX, LUA_TTABLE,
+ LUA_THREAD_REGISTRY_KEY);
+ if (ret < 1)
+ luaL_error(L, "lua threads table is %s",
+ (ret == 0) ? "missing" : "not a table");
+
+ /* get the TLS-table */
+ ret = dlua_table_get_by_thread(L, -1, LUA_TTABLE);
+ if (ret < 1)
+ luaL_error(L, "lua TLS table for thread %p is not a table", L);
+
+ /* stack: threads-table, TLS-table (top) */
+
+ /* remove threads-table from stack */
+ lua_remove(L, -2);
+}
+
+void dlua_tls_set_ptr(lua_State *L, const char *name, void *ptr)
+{
+ get_tls_table(L);
+ lua_pushlightuserdata(L, ptr);
+ lua_setfield(L, -2, name);
+ lua_pop(L, 1);
+}
+
+void *dlua_tls_get_ptr(lua_State *L, const char *name)
+{
+ void *ptr;
+
+ get_tls_table(L);
+ lua_getfield(L, -1, name);
+
+ ptr = lua_touserdata(L, -1);
+
+ lua_pop(L, 2);
+
+ return ptr;
+}
+
+void dlua_tls_set_int(lua_State *L, const char *name, lua_Integer i)
+{
+ get_tls_table(L);
+ lua_pushinteger(L, i);
+ lua_setfield(L, -2, name);
+ lua_pop(L, 1);
+}
+
+lua_Integer dlua_tls_get_int(lua_State *L, const char *name)
+{
+ lua_Integer i;
+
+ get_tls_table(L);
+ lua_getfield(L, -1, name);
+
+ i = lua_tointeger(L, -1);
+
+ lua_pop(L, 2);
+
+ return i;
+}
+
+void dlua_tls_clear(lua_State *L, const char *name)
+{
+ get_tls_table(L);
+ lua_pushnil(L);
+ lua_setfield(L, -2, name);
+ lua_pop(L, 1);
+}