From 2450bc617489c953f1472dbcba6c98fb1081e352 Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Wed, 11 Feb 2026 17:36:12 -0500 Subject: [PATCH 01/11] TASK-194243 debug double unlock --- src/ldo.c | 3 +++ src/thrlua.h | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/ldo.c b/src/ldo.c index a64e320..7b3b270 100644 --- a/src/ldo.c +++ b/src/ldo.c @@ -69,6 +69,9 @@ void luaD_throw (lua_State *L, int errcode) { LUAI_THROW(L, L->errorJmp); } else { + thrlua_log(L, DCRITICAL, + "luaD_throw: no error handler, errcode=%d, L=%p, invoking panic", + errcode, (void *)L); L->status = cast_byte(errcode); if (G(L)->panic) { resetstack(L, errcode); diff --git a/src/thrlua.h b/src/thrlua.h index 3f39d8f..6d69bcc 100644 --- a/src/thrlua.h +++ b/src/thrlua.h @@ -254,6 +254,22 @@ extern void lua_do_longjmp(luai_jmpbuf env, int val) #define LUAI_TRY_END(L) \ (L)->errorJmp = lj.previous; \ if (lj.status) { \ + /* Log the error and re-throw path for diagnostics. */ \ + thrlua_log((L), DCRITICAL, \ + "LUAI_TRY_END: error status=%d in TRY block at %s:%d, " \ + "re-throwing with %s outer handler, L=%p", \ + lj.status, lj.file, lj.line, \ + lj.previous ? "an" : "NO", (void *)(L)); \ + /* Re-acquire the lock before re-throwing. The LUAI_TRY_FINALLY \ + * block has already called lua_unlock(), but luaD_throw() expects \ + * the lock to be held: if there is an outer error handler its \ + * FINALLY will lua_unlock(), and if there is no outer handler the \ + * panic path in luaD_throw() calls lua_unlock() explicitly. \ + * Without this re-lock we double-unlock the pthread mutex which \ + * is undefined behavior and corrupts the mutex on Linux/glibc, \ + * causing every subsequent lua_lock() on this lua_State to fail \ + * with EINVAL and abort(). */ \ + lua_lock((L)); \ luaD_throw((L), lj.status); \ } \ } while(0) From d5b2b1bb2027c1a984280e4828f0dd9b3eada154 Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Thu, 12 Feb 2026 15:41:03 -0500 Subject: [PATCH 02/11] TASK-194243 fix double unlock --- src/ldo.c | 12 +++++++++++- src/thrlua.h | 10 ---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ldo.c b/src/ldo.c index 7b3b270..4e8abb7 100644 --- a/src/ldo.c +++ b/src/ldo.c @@ -75,7 +75,17 @@ void luaD_throw (lua_State *L, int errcode) { L->status = cast_byte(errcode); if (G(L)->panic) { resetstack(L, errcode); - lua_unlock(L); + /* Do NOT call lua_unlock(L) here. When luaD_throw is reached + * via LUAI_TRY_END re-throw, the LUAI_TRY_FINALLY block has + * already run and the lock state depends on the call site: + * some FINALLY blocks call lua_unlock (lapi.c, lauxlib.c) while + * others call lua_lock (ldo.c callhook/precall). Unconditionally + * unlocking here caused a double-unlock that corrupted the + * pthread mutex on Linux/glibc, making every subsequent + * lua_lock() on this lua_State fail with EINVAL and abort(). + * All direct callers of luaD_throw (lmem.c, ldebug.c, lundump.c, + * llex.c) always have an outer errorJmp so they never reach + * this panic branch. */ G(L)->panic(L); } exit(EXIT_FAILURE); diff --git a/src/thrlua.h b/src/thrlua.h index 6d69bcc..b275f39 100644 --- a/src/thrlua.h +++ b/src/thrlua.h @@ -260,16 +260,6 @@ extern void lua_do_longjmp(luai_jmpbuf env, int val) "re-throwing with %s outer handler, L=%p", \ lj.status, lj.file, lj.line, \ lj.previous ? "an" : "NO", (void *)(L)); \ - /* Re-acquire the lock before re-throwing. The LUAI_TRY_FINALLY \ - * block has already called lua_unlock(), but luaD_throw() expects \ - * the lock to be held: if there is an outer error handler its \ - * FINALLY will lua_unlock(), and if there is no outer handler the \ - * panic path in luaD_throw() calls lua_unlock() explicitly. \ - * Without this re-lock we double-unlock the pthread mutex which \ - * is undefined behavior and corrupts the mutex on Linux/glibc, \ - * causing every subsequent lua_lock() on this lua_State to fail \ - * with EINVAL and abort(). */ \ - lua_lock((L)); \ luaD_throw((L), lj.status); \ } \ } while(0) From 8d605612fcab2a413581bc438634f34541ec30d4 Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 08:42:35 -0500 Subject: [PATCH 03/11] TASK-194243 fix logging --- src/lapi.c | 4 ++++ src/ldo.c | 2 +- src/thrlua.h | 16 ++++++++++------ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/lapi.c b/src/lapi.c index bb07ca8..ee02ef2 100644 --- a/src/lapi.c +++ b/src/lapi.c @@ -1233,6 +1233,10 @@ LUA_API int lua_error (lua_State *L) { lua_lock(L); LUAI_TRY_BLOCK(L) { api_checknelems(L, 1); + { + const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; + thrlua_log(L, DCRITICAL, "lua_error called: %s\n", msg); + } luaG_errormsg(L); } LUAI_TRY_FINALLY(L) { lua_unlock(L); diff --git a/src/ldo.c b/src/ldo.c index 4e8abb7..9b2db4c 100644 --- a/src/ldo.c +++ b/src/ldo.c @@ -70,7 +70,7 @@ void luaD_throw (lua_State *L, int errcode) { } else { thrlua_log(L, DCRITICAL, - "luaD_throw: no error handler, errcode=%d, L=%p, invoking panic", + "luaD_throw: no error handler, errcode=%d, L=%p, invoking panic\n", errcode, (void *)L); L->status = cast_byte(errcode); if (G(L)->panic) { diff --git a/src/thrlua.h b/src/thrlua.h index b275f39..aa8a217 100644 --- a/src/thrlua.h +++ b/src/thrlua.h @@ -254,12 +254,16 @@ extern void lua_do_longjmp(luai_jmpbuf env, int val) #define LUAI_TRY_END(L) \ (L)->errorJmp = lj.previous; \ if (lj.status) { \ - /* Log the error and re-throw path for diagnostics. */ \ - thrlua_log((L), DCRITICAL, \ - "LUAI_TRY_END: error status=%d in TRY block at %s:%d, " \ - "re-throwing with %s outer handler, L=%p", \ - lj.status, lj.file, lj.line, \ - lj.previous ? "an" : "NO", (void *)(L)); \ + /* Only log when there is NO outer handler -- that is the \ + * dangerous path that hits luaD_throw's panic branch. \ + * Normal error propagation (with an outer handler) is \ + * high-frequency and must not be logged. */ \ + if (!lj.previous) { \ + thrlua_log((L), DCRITICAL, \ + "LUAI_TRY_END: error status=%d in TRY block at %s:%d, " \ + "re-throwing with NO outer handler, L=%p\n", \ + lj.status, lj.file, lj.line, (void *)(L)); \ + } \ luaD_throw((L), lj.status); \ } \ } while(0) From 6a8c24c2cfc00ebb376ef5fdb3462d41de42c9e9 Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 08:52:05 -0500 Subject: [PATCH 04/11] TASK-194243 fix logging --- src/lapi.c | 4 ---- src/ldebug.c | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lapi.c b/src/lapi.c index ee02ef2..bb07ca8 100644 --- a/src/lapi.c +++ b/src/lapi.c @@ -1233,10 +1233,6 @@ LUA_API int lua_error (lua_State *L) { lua_lock(L); LUAI_TRY_BLOCK(L) { api_checknelems(L, 1); - { - const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; - thrlua_log(L, DCRITICAL, "lua_error called: %s\n", msg); - } luaG_errormsg(L); } LUAI_TRY_FINALLY(L) { lua_unlock(L); diff --git a/src/ldebug.c b/src/ldebug.c index ec60a37..98907cb 100644 --- a/src/ldebug.c +++ b/src/ldebug.c @@ -631,6 +631,11 @@ void luaG_errormsg (lua_State *L) { incr_top(L); luaD_call(L, L->top - 2, 1); /* call it */ } + { + const char *msg = (L->top > L->base && ttisstring(L->top - 1)) + ? svalue(L->top - 1) : "(non-string error)"; + thrlua_log(L, DCRITICAL, "luaG_errormsg: %s\n", msg); + } luaD_throw(L, LUA_ERRRUN); } From 4fbebf52d7ec4c83048d1079636a4a29daa6bc1b Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 10:10:21 -0500 Subject: [PATCH 05/11] TASK-194243 tune logging --- src/lapi.c | 4 ++++ src/ldebug.c | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lapi.c b/src/lapi.c index bb07ca8..ace347d 100644 --- a/src/lapi.c +++ b/src/lapi.c @@ -1232,6 +1232,10 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { LUA_API int lua_error (lua_State *L) { lua_lock(L); LUAI_TRY_BLOCK(L) { + { +// const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; +// thrlua_log(L, DCRITICAL, "lua_error called: %s\n", msg); + } api_checknelems(L, 1); luaG_errormsg(L); } LUAI_TRY_FINALLY(L) { diff --git a/src/ldebug.c b/src/ldebug.c index 98907cb..34103f8 100644 --- a/src/ldebug.c +++ b/src/ldebug.c @@ -631,7 +631,12 @@ void luaG_errormsg (lua_State *L) { incr_top(L); luaD_call(L, L->top - 2, 1); /* call it */ } - { + /* Only log when there is no error handler -- that means the + * subsequent luaD_throw will hit the panic branch. When + * L->errorJmp is set the error will be caught by pcall/xpcall + * and is part of normal control flow (e.g. lua-aws uses pcall + * for method-existence checks). */ + if (!L->errorJmp) { const char *msg = (L->top > L->base && ttisstring(L->top - 1)) ? svalue(L->top - 1) : "(non-string error)"; thrlua_log(L, DCRITICAL, "luaG_errormsg: %s\n", msg); From 657cda24cea876fcd03b9828fc43ccf060f9f343 Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 10:39:42 -0500 Subject: [PATCH 06/11] TASK-194243 tune logging --- src/lapi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lapi.c b/src/lapi.c index ace347d..aabfc20 100644 --- a/src/lapi.c +++ b/src/lapi.c @@ -1233,10 +1233,11 @@ LUA_API int lua_error (lua_State *L) { lua_lock(L); LUAI_TRY_BLOCK(L) { { -// const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; -// thrlua_log(L, DCRITICAL, "lua_error called: %s\n", msg); + const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; + thrlua_log(L, DCRITICAL, "lua_error called 1: %s\n", msg); } api_checknelems(L, 1); + thrlua_log(L, DCRITICAL, "lua_error called 2\n", ); luaG_errormsg(L); } LUAI_TRY_FINALLY(L) { lua_unlock(L); From e1bf5186ccc0753e6bdae5af05c3c7e48401e5bd Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 10:43:55 -0500 Subject: [PATCH 07/11] TASK-194243 tune logging --- src/lapi.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lapi.c b/src/lapi.c index aabfc20..60125d1 100644 --- a/src/lapi.c +++ b/src/lapi.c @@ -1232,12 +1232,10 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { LUA_API int lua_error (lua_State *L) { lua_lock(L); LUAI_TRY_BLOCK(L) { - { const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; thrlua_log(L, DCRITICAL, "lua_error called 1: %s\n", msg); - } api_checknelems(L, 1); - thrlua_log(L, DCRITICAL, "lua_error called 2\n", ); + thrlua_log(L, DCRITICAL, "lua_error called %s\n", "end"); luaG_errormsg(L); } LUAI_TRY_FINALLY(L) { lua_unlock(L); From ca92e6ccaa17af34532082c2ecce15fb054f310f Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 11:29:41 -0500 Subject: [PATCH 08/11] TASK-194243 tune logging --- src/lapi.c | 8 +++++--- src/ldebug.c | 10 +++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/lapi.c b/src/lapi.c index 60125d1..334fa0b 100644 --- a/src/lapi.c +++ b/src/lapi.c @@ -1232,10 +1232,12 @@ LUA_API int lua_gc (lua_State *L, int what, int data) { LUA_API int lua_error (lua_State *L) { lua_lock(L); LUAI_TRY_BLOCK(L) { - const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; - thrlua_log(L, DCRITICAL, "lua_error called 1: %s\n", msg); api_checknelems(L, 1); - thrlua_log(L, DCRITICAL, "lua_error called %s\n", "end"); + { + const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; + thrlua_log(L, DCRITICAL, "lua_error (%s): %s\n", + L->errorJmp ? "caught" : "unhandled", msg); + } luaG_errormsg(L); } LUAI_TRY_FINALLY(L) { lua_unlock(L); diff --git a/src/ldebug.c b/src/ldebug.c index 34103f8..3b40aff 100644 --- a/src/ldebug.c +++ b/src/ldebug.c @@ -631,15 +631,11 @@ void luaG_errormsg (lua_State *L) { incr_top(L); luaD_call(L, L->top - 2, 1); /* call it */ } - /* Only log when there is no error handler -- that means the - * subsequent luaD_throw will hit the panic branch. When - * L->errorJmp is set the error will be caught by pcall/xpcall - * and is part of normal control flow (e.g. lua-aws uses pcall - * for method-existence checks). */ - if (!L->errorJmp) { + { const char *msg = (L->top > L->base && ttisstring(L->top - 1)) ? svalue(L->top - 1) : "(non-string error)"; - thrlua_log(L, DCRITICAL, "luaG_errormsg: %s\n", msg); + thrlua_log(L, DCRITICAL, "luaG_errormsg (%s): %s\n", + L->errorJmp ? "caught" : "unhandled", msg); } luaD_throw(L, LUA_ERRRUN); } From 7f9f8047eca67faa9a56dd37a6223e9601d94323 Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 12:03:47 -0500 Subject: [PATCH 09/11] TASK-194243 tune logging --- src/lstrlib.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lstrlib.c b/src/lstrlib.c index 6693eb3..35bfd81 100644 --- a/src/lstrlib.c +++ b/src/lstrlib.c @@ -138,8 +138,16 @@ static int str_dump (lua_State *L) { luaL_checktype(L, 1, LUA_TFUNCTION); lua_settop(L, 1); luaL_buffinit(L,&b); - if (lua_dump(L, writer, &b) != 0) - luaL_error(L, "unable to dump given function"); + if (lua_dump(L, writer, &b) != 0) { + lua_Debug ar; + lua_pushvalue(L, 1); + lua_getinfo(L, ">nS", &ar); + luaL_error(L, "unable to dump given function (%s '%s' defined at %s:%d)", + ar.what ? ar.what : "?", + ar.name ? ar.name : "(anonymous)", + ar.short_src ? ar.short_src : "?", + ar.linedefined); + } luaL_pushresult(&b); return 1; } From 1aa55d7b69355df58c7983970509ff0cfafdb323 Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 12:30:14 -0500 Subject: [PATCH 10/11] TASK-194243 tune logging --- src/lapi.c | 13 +++++++++---- src/ldebug.c | 5 ++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lapi.c b/src/lapi.c index 334fa0b..de9719a 100644 --- a/src/lapi.c +++ b/src/lapi.c @@ -1233,10 +1233,15 @@ LUA_API int lua_error (lua_State *L) { lua_lock(L); LUAI_TRY_BLOCK(L) { api_checknelems(L, 1); - { - const char *msg = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(non-string error)"; - thrlua_log(L, DCRITICAL, "lua_error (%s): %s\n", - L->errorJmp ? "caught" : "unhandled", msg); + if (!L->errorJmp) { + /* Use raw accessors (ttisstring/svalue) instead of the public API + * (lua_isstring/lua_tostring). lua_isstring returns true for + * numbers, and lua_tostring -> lua_tolstring calls lua_lock for + * number-to-string conversion, which would deadlock since we + * already hold the lock from lua_error's lua_lock above. */ + StkId o = L->top - 1; + const char *msg = ttisstring(o) ? svalue(o) : "(non-string error)"; + thrlua_log(L, DCRITICAL, "lua_error (unhandled): %s\n", msg); } luaG_errormsg(L); } LUAI_TRY_FINALLY(L) { diff --git a/src/ldebug.c b/src/ldebug.c index 3b40aff..e17b490 100644 --- a/src/ldebug.c +++ b/src/ldebug.c @@ -631,11 +631,10 @@ void luaG_errormsg (lua_State *L) { incr_top(L); luaD_call(L, L->top - 2, 1); /* call it */ } - { + if (!L->errorJmp) { const char *msg = (L->top > L->base && ttisstring(L->top - 1)) ? svalue(L->top - 1) : "(non-string error)"; - thrlua_log(L, DCRITICAL, "luaG_errormsg (%s): %s\n", - L->errorJmp ? "caught" : "unhandled", msg); + thrlua_log(L, DCRITICAL, "luaG_errormsg (unhandled): %s\n", msg); } luaD_throw(L, LUA_ERRRUN); } From fbf904bf9d6cef840217b7ce301d9a7320cc90fb Mon Sep 17 00:00:00 2001 From: Balasubramania Pillai Date: Fri, 13 Feb 2026 12:51:30 -0500 Subject: [PATCH 11/11] TASK-194243 tune logging --- src/lapi.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lapi.c b/src/lapi.c index de9719a..cdfa967 100644 --- a/src/lapi.c +++ b/src/lapi.c @@ -1233,8 +1233,13 @@ LUA_API int lua_error (lua_State *L) { lua_lock(L); LUAI_TRY_BLOCK(L) { api_checknelems(L, 1); - if (!L->errorJmp) { - /* Use raw accessors (ttisstring/svalue) instead of the public API + if (!lj.previous) { + /* Log only when there is no outer error handler -- i.e. the error + * will ultimately reach the panic branch. We check lj.previous + * (not L->errorJmp) because LUAI_TRY_BLOCK already set + * L->errorJmp = &lj, so L->errorJmp is always non-NULL here. + * + * Use raw accessors (ttisstring/svalue) instead of the public API * (lua_isstring/lua_tostring). lua_isstring returns true for * numbers, and lua_tostring -> lua_tolstring calls lua_lock for * number-to-string conversion, which would deadlock since we