This is the test case I cooked up so far.
Two threads are trying to access a plugin simultaneously and each time the plugin calls a function in C++ the "lua thread" yields and does its thing which allows the other thread to execute the plugin.
Each time a "lua thread" yields, all locks for the plugin are released making it impossible to create a deadlock (even though a deadlock cannot even exist in this setting, even if I didn't use yielding)
I also figured out some cool template magic which makes it really easy to wrap plugins to support yielding.
main.cpp
test.lua
Two threads are trying to access a plugin simultaneously and each time the plugin calls a function in C++ the "lua thread" yields and does its thing which allows the other thread to execute the plugin.
Each time a "lua thread" yields, all locks for the plugin are released making it impossible to create a deadlock (even though a deadlock cannot even exist in this setting, even if I didn't use yielding)
I also figured out some cool template magic which makes it really easy to wrap plugins to support yielding.
main.cpp
cCriticalSection LuaStateLock; cCriticalSection LuaMessageLock; cCriticalSection LogLock; extern int tolua_StringSplit(lua_State* tolua_S); static int tolua_GetNumber(lua_State* a_pLuaState) { tolua_pushnumber( a_pLuaState, 1.234f ); return 1; } class cBaseMessage { public: virtual int Run() = 0; lua_State* pLuaState; }; typedef std::map<lua_State*, cBaseMessage*> LuaMessageMap; LuaMessageMap m_LuaMessages; template< int (*Func1)(lua_State *L) > static int Lua_Yield_Template( lua_State* a_pLuaState ) { class cLuaMessage : public cBaseMessage { public: virtual int Run() override { return Func1( pLuaState ); } }; cBaseMessage* pLuaMessage = new cLuaMessage(); pLuaMessage->pLuaState = a_pLuaState; LuaMessageLock.Lock(); m_LuaMessages[a_pLuaState] = pLuaMessage; LuaMessageLock.Unlock(); printf("top: %i\n", lua_gettop(a_pLuaState) ); return lua_yield(a_pLuaState, lua_gettop(a_pLuaState)); } // A function to make sure two threads cannot log at the same time (C++ vs Lua) static int LUA_SafeLog(lua_State* tolua_S) { cCSLock Lock( LogLock ); const char* str = tolua_tocppstring(tolua_S,1,0); printf("%s\n", str); return 0; } class cTestThread : public cIsThread { public: cTestThread( const AString & a_ThreadName ) : cIsThread( a_ThreadName ) {} void Execute(void) override { for (int i = 0; i < 400; ++i) { lua_getglobal(pLuaThread, FunctionToCall.c_str()); int res = LUA_YIELD; do { int nargs = 0; LuaMessageLock.Lock(); LuaMessageMap::iterator itr = m_LuaMessages.find(pLuaThread); if (itr != m_LuaMessages.end()) { cBaseMessage* pMessage = itr->second; m_LuaMessages.erase(itr); LuaMessageLock.Unlock(); LuaStateLock.Lock(); nargs = pMessage->Run(); LuaStateLock.Unlock(); delete pMessage; } else { LuaMessageLock.Unlock(); } LuaStateLock.Lock(); res = lua_resume(pLuaThread, nargs); LuaStateLock.Unlock(); if (res != LUA_YIELD && res != 0) { report_errors( pLuaThread, res ); } else if (res == 0) // no error, function returned { //cCSLock Lock(LogLock); //printf("returned type: %s %s\n", luaL_typename(pThread, -1) ); } } while (res == LUA_YIELD); } } lua_State* pLuaThread; int LuaThreadReferenceIdx; AString FunctionToCall; }; int main( int argc, char **argv ) { cTestThread TestThread1("TestThread1"); TestThread1.FunctionToCall = "fromOtherThread"; cTestThread TestThread2("TestThread2"); TestThread2.FunctionToCall = "myFunc"; lua_State* pLuaState = lua_open(); luaL_openlibs( pLuaState ); lua_register(pLuaState, "SafeLog", LUA_SafeLog); lua_register(pLuaState, "YieldGetNumber", Lua_Yield_Template<tolua_GetNumber> ); lua_register(pLuaState, "YieldStringSplit", Lua_Yield_Template<tolua_StringSplit> ); int s = luaL_loadfile(pLuaState, "Plugins/test.lua" ); s = lua_pcall(pLuaState, 0, LUA_MULTRET, 0); for (int i = 0; i < 5; ++i) { LuaStateLock.Lock(); TestThread1.pLuaThread = lua_newthread(pLuaState); TestThread1.LuaThreadReferenceIdx = luaL_ref(pLuaState, LUA_REGISTRYINDEX); TestThread2.pLuaThread = lua_newthread(pLuaState); TestThread2.LuaThreadReferenceIdx = luaL_ref(pLuaState, LUA_REGISTRYINDEX); LuaStateLock.Unlock(); TestThread1.Start(); TestThread2.Start(); TestThread1.Wait(); TestThread2.Wait(); luaL_unref(pLuaState,LUA_REGISTRYINDEX,TestThread1.LuaThreadReferenceIdx ); luaL_unref(pLuaState,LUA_REGISTRYINDEX,TestThread2.LuaThreadReferenceIdx ); } printf("Lua stack size: %i\n", lua_gettop(pLuaState)); lua_close( pLuaState ); }
test.lua
local localVar = 0 function GetLocalVar() localVar = localVar + 1 return localVar; end function myFunc() SafeLog("in myfunc " .. GetLocalVar()); local blaa = YieldGetNumber() SafeLog("blaa=" .. blaa) SafeLog("after yielding " .. GetLocalVar()) return "num: " .. blaa; end function fromOtherThread() SafeLog("in other thread " .. GetLocalVar()) local split = YieldStringSplit("one,2,three", ",") SafeLog("split: [" .. split[1] .. " - " .. split[2] .. " - " .. split[3] .. "]" ) end SafeLog("lulwut")