Commit 9ad11881cb for freeswitch.com
commit 9ad11881cb0255d3f0f5623d90eadf373540442a
Author: Andrey Volk <andywolk@gmail.com>
Date: Mon Oct 26 21:26:46 2026 +0300
[mod_lua] Fix crash on concurrent first Lua run by initializing SWIG type tables once at module load (#3245)
diff --git a/src/mod/languages/mod_lua/mod_lua.cpp b/src/mod/languages/mod_lua/mod_lua.cpp
index b1d19428dc..d0b39ba1d8 100644
--- a/src/mod/languages/mod_lua/mod_lua.cpp
+++ b/src/mod/languages/mod_lua/mod_lua.cpp
@@ -680,6 +680,17 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_lua_load)
switch_application_interface_t *app_interface;
switch_dialplan_interface_t *dp_interface;
switch_chat_application_interface_t *chat_app_interface;
+ lua_State *L;
+
+ /* The first luaopen_freeswitch() call fills SWIG's process-wide type tables and is not thread-safe,
+ so do it once here, before anything (calls, event hooks, startup scripts) can run Lua concurrently. */
+ if (!(L = lua_init())) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to initialize Lua\n");
+
+ return SWITCH_STATUS_FALSE;
+ }
+
+ lua_uninit(L);
/* connect my internal structure to the blank pointer passed to me */
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
diff --git a/src/mod/languages/mod_lua/test/test_mod_lua.c b/src/mod/languages/mod_lua/test/test_mod_lua.c
index bbd4cba584..3652c5c2b2 100644
--- a/src/mod/languages/mod_lua/test/test_mod_lua.c
+++ b/src/mod/languages/mod_lua/test/test_mod_lua.c
@@ -30,6 +30,36 @@
#include <test/switch_test.h>
+#define CONCURRENT_RUN_THREADS 32
+
+static switch_mutex_t *concurrent_run_mutex = NULL;
+static switch_thread_cond_t *concurrent_run_cond = NULL;
+static int concurrent_run_ready = 0;
+static int concurrent_run_go = 0;
+
+static void *SWITCH_THREAD_FUNC concurrent_run_thread(switch_thread_t *thread, void *obj)
+{
+ int *ok = (int *)obj;
+ switch_stream_handle_t stream = { 0 };
+
+ SWITCH_STANDARD_STREAM(stream);
+
+ /* Block until every thread is up, so that one broadcast sends them all into lua_init() together */
+ switch_mutex_lock(concurrent_run_mutex);
+ concurrent_run_ready++;
+ switch_thread_cond_broadcast(concurrent_run_cond);
+ while (!concurrent_run_go) {
+ switch_thread_cond_wait(concurrent_run_cond, concurrent_run_mutex);
+ }
+ switch_mutex_unlock(concurrent_run_mutex);
+
+ switch_api_execute("lua", "~stream:write('+OK')", NULL, &stream);
+ *ok = stream.data && !strcmp((char *)stream.data, "+OK");
+ switch_safe_free(stream.data);
+
+ return NULL;
+}
+
FST_CORE_BEGIN("conf")
{
FST_MODULE_BEGIN(mod_lua, mod_lua_test)
@@ -40,6 +70,44 @@ FST_CORE_BEGIN("conf")
}
FST_SETUP_END()
+ /* Keep this test first: only the very first luaopen_freeswitch() in the process can race on SWIG's type tables */
+ FST_TEST_BEGIN(concurrent_first_run_test)
+ {
+ switch_thread_t *thread[CONCURRENT_RUN_THREADS] = { 0 };
+ int ok[CONCURRENT_RUN_THREADS] = { 0 };
+ switch_threadattr_t *thd_attr = NULL;
+ switch_status_t status;
+ int i, started = 0;
+
+ switch_mutex_init(&concurrent_run_mutex, SWITCH_MUTEX_DEFAULT, fst_pool);
+ switch_thread_cond_create(&concurrent_run_cond, fst_pool);
+ switch_threadattr_create(&thd_attr, fst_pool);
+ switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
+
+ for (i = 0; i < CONCURRENT_RUN_THREADS; i++) {
+ if (switch_thread_create(&thread[started], thd_attr, concurrent_run_thread, &ok[started], fst_pool) == SWITCH_STATUS_SUCCESS) {
+ started++;
+ }
+ }
+
+ fst_check_int_equals(started, CONCURRENT_RUN_THREADS);
+
+ /* Only wait for the threads that did start, so a failed switch_thread_create() fails the test instead of hanging it */
+ switch_mutex_lock(concurrent_run_mutex);
+ while (concurrent_run_ready < started) {
+ switch_thread_cond_wait(concurrent_run_cond, concurrent_run_mutex);
+ }
+ concurrent_run_go = 1;
+ switch_thread_cond_broadcast(concurrent_run_cond);
+ switch_mutex_unlock(concurrent_run_mutex);
+
+ for (i = 0; i < started; i++) {
+ switch_thread_join(&status, thread[i]);
+ fst_check(ok[i]);
+ }
+ }
+ FST_TEST_END()
+
FST_TEST_BEGIN(json_test)
{
switch_stream_handle_t stream = { 0 };