// LuaThreadStress.cpp // Implements a stress-test of cLuaState under several threads #include "Globals.h" #include "Bindings/LuaState.h" #include #include /** How long the threading test should run. */ static const int NUM_SECONDS_TO_TEST = 10; /** Retrieves a callback from the Lua state that can be later called. Calls the Lua function getCallback with a_Seed param to retrieve the callback. */ static cLuaState::cCallbackPtr getCallback(cLuaState & a_LuaState, unsigned a_Seed) { cLuaState::cLock lock(a_LuaState); cLuaState::cCallbackPtr res; a_LuaState.Call("getCallback", a_Seed, cLuaState::Return, res); return res; } /** Runs a single thread that stress-tests the cLuaState object. a_LuaState is the Lua state on which to operate. a_Seed is the seed for the random number generator for this thread. a_ShouldTerminate is a bool flag that another thread sets to ask this thread to terminate. a_FailResult is a shared result state that is written by any thread upon failure (so if it contains nonzero, at least one thread has failed). */ static void runStress(cLuaState * a_LuaState, unsigned a_Seed, std::atomic * a_ShouldTerminate, std::atomic * a_FailResult) { std::minstd_rand rnd; rnd.seed(a_Seed); auto callbackSeed = static_cast(rnd()); auto callback = getCallback(*a_LuaState, callbackSeed); while (!a_ShouldTerminate->load()) { // Pick a random operation on the Lua state and peform it: switch (rnd() % 4) { case 0: { // Get a new callback: callbackSeed = callbackSeed + 1; callback = getCallback(*a_LuaState, callbackSeed); break; } default: { // Call the callback, if still available: auto param = static_cast(rnd()); unsigned returnValue; if (callback->Call(param, cLuaState::Return, returnValue)) { if (returnValue != param + callbackSeed) { LOGWARNING("Bad value returned from the callback"); *a_FailResult = 2; a_ShouldTerminate->store(true); return; } } break; } } // switch (random) // Once in every ~10k operations, reload the lua state completely: if ((rnd() % 10000) == 0) { cLuaState::cLock lock(*a_LuaState); a_LuaState->Close(); a_LuaState->Create(); if (!a_LuaState->LoadFile("Test.lua")) { *a_FailResult = 3; a_ShouldTerminate->store(true); return; } } } // while (!a_ShouldTerminate) } static int DoTest(void) { cLuaState L("LuaThreadStress test"); L.Create(); if (!L.LoadFile("Test.lua")) { return 1; } // Start the concurrect threads: std::atomic shouldTerminate(false); std::atomic failResult(0); std::thread threads[] = { std::thread(runStress, &L, 0, &shouldTerminate, &failResult), std::thread(runStress, &L, 1, &shouldTerminate, &failResult), std::thread(runStress, &L, 2, &shouldTerminate, &failResult), std::thread(runStress, &L, 3, &shouldTerminate, &failResult), }; // Let the threads run wild: for (int i = 1; i <= NUM_SECONDS_TO_TEST; ++i) { std::this_thread::sleep_for(std::chrono::seconds(1)); LOG("Testing (%d out of %d seconds)...", i, NUM_SECONDS_TO_TEST); } // Terminate everything: LOG("Terminating the threads"); shouldTerminate = true; for (auto & t: threads) { t.join(); } LOG("Threads terminated."); return failResult.load(); } int main() { LOG("LuaThreadStress starting."); int res = DoTest(); LOG("LuaThreadStress test done: %s", (res == 0) ? "success" : "failure"); if (res != 0) { return res; } LOG("LuaThreadStress finished."); return 0; }