• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!
  • 2026 staff recruitment is open! Check it out and consider applying!

LuaJIT Breaking Change

fusion32

Intermediate OT User
Joined
Jun 7, 2010
Messages
39
Solutions
2
Reaction score
113
For anyone using LuaJIT, there has been a breaking change with commit 5c64775. In summary, finalizers (__gc metamethod) now run on a separate lua thread, which causes the lua state for these callbacks to be different from the main lua state. This is only really a problem if the interfacing code assumes the lua state is always the same, which is the case for the OTClient.

For a concrete example on the OTClient, LuaInterface::luaCppFunctionCallback is used as the wrapping callback anytime you use LuaInterface::pushCppFunction, which is then used when setting the __gc metamethod for classes declared with LuaInterface::registerClass.
Code:
int LuaInterface::luaCppFunctionCallback(lua_State *L){
    void *funcPtr = g_lua.popUpvalueUserdata();             // fails in finalizers: assumes L == g_lua.L
    void *funcPtr = lua_touserdata(L, lua_upvalueindex(1)); // works in finalizers
    assert(funcPtr != NULL);
    //...
}

I have created an issue (#1420) on the LuaJIT issue tracker but it's unlikely that this change will be reversed, since it was dismissed as bad design (which I don't completely disagree). The simplest solution would be to use some version from before the problematic commit (which is from Nov 10 2025). Another solution would be to have specific callbacks for finalizers which only access the explicit lua state parameter. And the most obvious but more involved solution would be to avoid assuming the lua state for any callbacks.

Anyway, I'm not gonna try to guess which solution is better. I just figured I should make this problem known before repos like VCPKG on Windows or APT on Debian/Ubuntu get an updated version and all of a sudden stuff starts breaking.

PS: I haven't tested it with TFS or OT but from a quick glance, their callbacks don't assume the lua state, so it wouldn't apply then.
 
I've been asked to fix some stuff in OTC client recently and we had to find a fix for it to work with recent LuaJIT, However it came up that the fix is quite straightforward
C++:
// LuaJIT 2025+ (commit 5c64775) runs __gc finalizers on a separate vmthread,
// not on the main lua_State. Every entry point that LuaJIT calls back into must
// temporarily rebind g_lua.L to the state LuaJIT passed in, then restore on exit.
// Any new static C entry point registered with lua must begin with LUA_STATE_GUARD().
struct LuaStateGuard {
    lua_State*& ref;
    lua_State* saved;
    LuaStateGuard(lua_State*& ref, lua_State* newState) : ref(ref), saved(ref) { ref = newState; }
    ~LuaStateGuard() { ref = saved; }
};
#define LUA_STATE_GUARD() LuaStateGuard _stateGuard(g_lua.L, L)

and then you just need to add LUA_STATE_GUARD(); to begining of functions that uses lua state. In our case it's about 10~ functions all in luainterface.cpp

This is example
C++:
int LuaInterface::luaCppFunctionCallback(lua_State* L)
{
    LUA_STATE_GUARD();
    // retrieves function pointer from userdata
    const auto* const funcPtr = static_cast<LuaCppFunctionPtr*>(g_lua.popUpvalueUserdata());
    assert(funcPtr);

    int numRets = 0;

    // do the call
    try {
        ++g_lua.m_cppCallbackDepth;
        numRets = (*(funcPtr->get()))(&g_lua);
        --g_lua.m_cppCallbackDepth;
        assert(numRets == g_lua.stackSize());
    } catch (stdext::exception& e) {
        --g_lua.m_cppCallbackDepth;
        // cleanup stack
        while (g_lua.stackSize() > 0)
            g_lua.pop();
        numRets = 0;
        g_lua.pushString(fmt::format("C++ call failed: {}", g_lua.traceback(e.what())));
        g_lua.error();
    } catch (const std::exception& e) {
        --g_lua.m_cppCallbackDepth;
        while (g_lua.stackSize() > 0)
            g_lua.pop();
        numRets = 0;
        g_lua.pushString(fmt::format("C++ std::exception: {}", g_lua.traceback(e.what())));
        g_lua.error();
    } catch (...) {
        --g_lua.m_cppCallbackDepth;
        while (g_lua.stackSize() > 0)
            g_lua.pop();
        numRets = 0;
        g_lua.pushString(g_lua.traceback("Unknown C++ exception"));
        g_lua.error();
    }

    return numRets;
}
 
Back
Top