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")

