summaryrefslogtreecommitdiffstats
path: root/src/lib-lua/dlua-resume.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-lua/dlua-resume.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-lua/dlua-resume.c')
-rw-r--r--src/lib-lua/dlua-resume.c208
1 files changed, 208 insertions, 0 deletions
diff --git a/src/lib-lua/dlua-resume.c b/src/lib-lua/dlua-resume.c
new file mode 100644
index 0000000..ac46f10
--- /dev/null
+++ b/src/lib-lua/dlua-resume.c
@@ -0,0 +1,208 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "dlua-script-private.h"
+
+#define PCALL_RESUME_STATE "pcall-resume-state"
+
+#define RESUME_TIMEOUT "resume-timeout"
+#define RESUME_NARGS "resume-nargs"
+
+struct dlua_pcall_resume_state {
+ dlua_pcall_yieldable_callback_t *callback;
+ void *context;
+ struct timeout *to;
+ int status;
+};
+
+#ifdef DLUA_WITH_YIELDS
+static void call_resume_callback(lua_State *L)
+{
+ struct dlua_pcall_resume_state *state = dlua_tls_get_ptr(L, PCALL_RESUME_STATE);
+
+ timeout_remove(&state->to);
+
+ dlua_tls_clear(L, PCALL_RESUME_STATE);
+
+ state->callback(L, state->context, state->status);
+
+ i_free(state);
+}
+
+static void queue_resume_callback(lua_State *L, int status)
+{
+ struct dlua_pcall_resume_state *state = dlua_tls_get_ptr(L, PCALL_RESUME_STATE);
+
+ i_assert(status != LUA_YIELD);
+
+ if (status != LUA_OK) {
+ int ret;
+
+ /* error occured: run debug.traceback() */
+
+ /* stack: ..., error (top) */
+ lua_getglobal(L, "debug");
+
+ /* stack: ..., error, debug table (top) */
+ lua_getfield(L, -1, "traceback");
+
+ /* stack: ..., error, debug table, traceback function (top) */
+ lua_remove(L, -2);
+
+ /* stack: ..., error, traceback function (top) */
+ lua_pushvalue(L, -2); /* duplicate original error */
+
+ /* stack: ..., error, traceback function, error (top) */
+
+ /*
+ * Note that we kept the original error on the stack as well
+ * as passed it to debug.traceback(). The reason for that
+ * is that debug.traceback() itself can fail. If it fails,
+ * it'll generate its own error - which, ultimately, we
+ * don't care about. For example, consider the following
+ * function:
+ *
+ * function foo()
+ * debug.traceback = nil
+ * error("abc")
+ * end
+ *
+ * If we executed this function, it would error out - but
+ * it'd also cause our pcall to debug.traceback() to fail
+ * with "attempt to call a nil value". We want to discard
+ * the nil error, and just use the original ("abc"). This
+ * is ok because debug.traceback() simply "improves" the
+ * passed in error message to include a traceback and no
+ * traceback is better than a very mysterious error message.
+ */
+ ret = lua_pcall(L, 1, 1, 0);
+
+ /* stack: ..., orig error, traceback result/error (top) */
+
+ if (ret != LUA_OK) {
+ /* traceback failed, remove its error */
+ lua_remove(L, -1);
+ } else {
+ /* traceback succeeded, remove original error */
+ lua_remove(L, -2);
+ }
+ }
+
+ /*
+ * Mangle the passed in status to match dlua_pcall(). Namely, turn
+ * it into -1 on error, and 0+ to indicate the number of return
+ * values.
+ */
+ if (status == LUA_OK)
+ state->status = lua_gettop(L);
+ else
+ state->status = -1;
+
+ i_assert(state->to == NULL);
+ state->to = timeout_add_short(0, call_resume_callback, L);
+}
+
+static void dlua_pcall_yieldable_continue(lua_State *L)
+{
+ struct timeout *to;
+ int nargs, nresults;
+ int ret;
+
+ nargs = dlua_tls_get_int(L, RESUME_NARGS);
+ to = dlua_tls_get_ptr(L, RESUME_TIMEOUT);
+
+ timeout_remove(&to);
+
+ dlua_tls_clear(L, RESUME_TIMEOUT);
+ dlua_tls_clear(L, RESUME_NARGS);
+
+ ret = lua_resume(L, L, nargs, &nresults);
+ if (ret == LUA_YIELD) {
+ /*
+ * thread yielded - nothing to do
+ *
+ * We assume something will call lua_resume(). We don't
+ * care if it is a io related callback or just a timeout.
+ */
+ } else if (ret == LUA_OK) {
+ /* thread completed - invoke callback */
+ queue_resume_callback(L, ret);
+ } else {
+ /* error occurred - invoke callback */
+ queue_resume_callback(L, ret);
+ }
+}
+
+void dlua_pcall_yieldable_resume(lua_State *L, int nargs)
+{
+ struct timeout *to;
+
+ to = timeout_add_short(0, dlua_pcall_yieldable_continue, L);
+
+ dlua_tls_set_ptr(L, RESUME_TIMEOUT, to);
+ dlua_tls_set_int(L, RESUME_NARGS, nargs);
+}
+
+/*
+ * Call a function with nargs arguments in a way that supports yielding.
+ * When the function execution completes, the passed in callback is called.
+ *
+ * Returns -1 on error or 0 on success.
+ */
+#undef dlua_pcall_yieldable
+int dlua_pcall_yieldable(lua_State *L, const char *func_name, int nargs,
+ dlua_pcall_yieldable_callback_t *callback,
+ void *context, const char **error_r)
+{
+ struct dlua_pcall_resume_state *state;
+ int ret;
+ int nresults;
+
+ i_assert(lua_status(L) == LUA_OK);
+
+ lua_getglobal(L, func_name);
+
+ if (!lua_isfunction(L, -1)) {
+ /* clean up the stack - function + arguments */
+ lua_pop(L, nargs + 1);
+ *error_r = t_strdup_printf("'%s' is not a function", func_name);
+ return -1;
+ }
+
+ /* allocate and stash in TLS callback state */
+ state = i_new(struct dlua_pcall_resume_state, 1);
+ state->callback = callback;
+ state->context = context;
+
+ dlua_tls_set_ptr(L, PCALL_RESUME_STATE, state);
+
+ /* stack: args, func (top) */
+ lua_insert(L, -(nargs + 1));
+
+ /* stack: func, args (top) */
+ ret = lua_resume(L, L, nargs, &nresults);
+ if (ret == LUA_YIELD) {
+ /*
+ * thread yielded - nothing to do
+ *
+ * We assume something will call lua_resume(). We don't
+ * care if it is a io related callback or just a timeout.
+ */
+ } else {
+ /*
+ * thread completed / errored
+ *
+ * Since there is nothing that will come back to this lua
+ * thread, we need to make sure the callback is called.
+ *
+ * We handle errors the same as successful completion in
+ * order to avoid forcing the callers to check for lua
+ * errors in two places - the call here and in the callback.
+ */
+ queue_resume_callback(L, ret);
+ }
+
+ return 0;
+}
+#endif