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 };