From e9547101b1d95167657a9b470eef1f0029fcb7e6 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 15 Jan 2017 12:54:46 +0100 Subject: LuaState: Fixed untracking references. One thread may untrack a ref while another thread was closing the LuaState, which had emptied the tracked refs. --- src/Bindings/LuaState.cpp | 12 +++++++----- src/Bindings/LuaState.h | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index b768b21a3..2acf7df84 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -2288,15 +2288,17 @@ void cLuaState::UntrackRef(cTrackedRef & a_Ref) return; } - // Remove the callback: + // Remove the callback (note that another thread may have cleared the callbacks by closing the LuaState): cCSLock Lock(canonState->m_CSTrackedRefs); auto & trackedRefs = canonState->m_TrackedRefs; - trackedRefs.erase(std::remove_if(trackedRefs.begin(), trackedRefs.end(), - [&a_Ref](cTrackedRef * a_StoredRef) + for (auto itr = trackedRefs.begin(), end = trackedRefs.end(); itr != end; ++itr) + { + if (*itr == &a_Ref) { - return (a_StoredRef == &a_Ref); + trackedRefs.erase(itr); + break; } - )); + } } diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index 80f5c3cc6..558e8d79a 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -522,7 +522,8 @@ public: /** Allows this object to be used in the same way as a lua_State *, for example in the LuaLib functions */ operator lua_State * (void) { return m_LuaState; } - /** Creates the m_LuaState, if not closed already. This state will be automatically closed in the destructor. + /** Creates the m_LuaState, if not created already. + This state will be automatically closed in the destructor. The regular Lua libs are registered, but the MCS API is not registered (so that Lua can be used as lite-config as well), use RegisterAPILibs() to do that. */ void Create(void); -- cgit v1.2.3 From 30756e3f952cfb790b5c5f503d82eea3ea938814 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 15 Jan 2017 15:11:18 +0100 Subject: Tests: Added LuaState thread stress-test. --- tests/CMakeLists.txt | 1 + tests/LuaThreadStress/Bindings.h | 15 ++ tests/LuaThreadStress/CMakeLists.txt | 97 +++++++++ tests/LuaThreadStress/LuaState_Declaration.inc | 4 + tests/LuaThreadStress/LuaState_Typedefs.inc | 19 ++ tests/LuaThreadStress/LuaThreadStress.cpp | 154 ++++++++++++++ tests/LuaThreadStress/Stubs.cpp | 273 +++++++++++++++++++++++++ tests/LuaThreadStress/Test.lua | 17 ++ 8 files changed, 580 insertions(+) create mode 100644 tests/LuaThreadStress/Bindings.h create mode 100644 tests/LuaThreadStress/CMakeLists.txt create mode 100644 tests/LuaThreadStress/LuaState_Declaration.inc create mode 100644 tests/LuaThreadStress/LuaState_Typedefs.inc create mode 100644 tests/LuaThreadStress/LuaThreadStress.cpp create mode 100644 tests/LuaThreadStress/Stubs.cpp create mode 100644 tests/LuaThreadStress/Test.lua diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0e7b51445..1010034fd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(CompositeChat) add_subdirectory(FastRandom) add_subdirectory(HTTP) add_subdirectory(LoadablePieces) +add_subdirectory(LuaThreadStress) add_subdirectory(Network) add_subdirectory(OSSupport) add_subdirectory(SchematicFileSerializer) diff --git a/tests/LuaThreadStress/Bindings.h b/tests/LuaThreadStress/Bindings.h new file mode 100644 index 000000000..490830ac3 --- /dev/null +++ b/tests/LuaThreadStress/Bindings.h @@ -0,0 +1,15 @@ + +// Bindings.h + +// Dummy include file needed for LuaState to compile successfully + + + + +struct lua_State; + +int tolua_AllToLua_open(lua_State * a_LuaState); + + + + diff --git a/tests/LuaThreadStress/CMakeLists.txt b/tests/LuaThreadStress/CMakeLists.txt new file mode 100644 index 000000000..402b77d23 --- /dev/null +++ b/tests/LuaThreadStress/CMakeLists.txt @@ -0,0 +1,97 @@ +enable_testing() + +include_directories(${CMAKE_SOURCE_DIR}/src/) +include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/lib/) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +add_definitions(-DTEST_GLOBALS=1) + +set (SHARED_SRCS + ${CMAKE_SOURCE_DIR}/src/BiomeDef.cpp + ${CMAKE_SOURCE_DIR}/src/BlockArea.cpp + ${CMAKE_SOURCE_DIR}/src/Cuboid.cpp + ${CMAKE_SOURCE_DIR}/src/ChunkData.cpp + ${CMAKE_SOURCE_DIR}/src/StringCompression.cpp + ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp + + ${CMAKE_SOURCE_DIR}/src/Bindings/LuaState.cpp + + ${CMAKE_SOURCE_DIR}/src/Generating/ChunkDesc.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/PieceGenerator.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/Prefab.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/PrefabPiecePool.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/VerticalLimit.cpp + ${CMAKE_SOURCE_DIR}/src/Generating/VerticalStrategy.cpp + + ${CMAKE_SOURCE_DIR}/src/Noise/Noise.cpp + + ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/File.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/GZipFile.cpp + ${CMAKE_SOURCE_DIR}/src/OSSupport/StackTrace.cpp + + ${CMAKE_SOURCE_DIR}/src/WorldStorage/FastNBT.cpp + ${CMAKE_SOURCE_DIR}/src/WorldStorage/SchematicFileSerializer.cpp +) + +set (SHARED_HDRS + ${CMAKE_SOURCE_DIR}/src/BiomeDef.h + ${CMAKE_SOURCE_DIR}/src/BlockArea.h + ${CMAKE_SOURCE_DIR}/src/Cuboid.h + ${CMAKE_SOURCE_DIR}/src/ChunkData.h + ${CMAKE_SOURCE_DIR}/src/Globals.h + ${CMAKE_SOURCE_DIR}/src/StringCompression.h + ${CMAKE_SOURCE_DIR}/src/StringUtils.h + + ${CMAKE_SOURCE_DIR}/src/Bindings/LuaState.h + + ${CMAKE_SOURCE_DIR}/src/Generating/ChunkDesc.h + ${CMAKE_SOURCE_DIR}/src/Generating/PieceGenerator.h + ${CMAKE_SOURCE_DIR}/src/Generating/Prefab.h + ${CMAKE_SOURCE_DIR}/src/Generating/PrefabPiecePool.h + ${CMAKE_SOURCE_DIR}/src/Generating/VerticalLimit.h + ${CMAKE_SOURCE_DIR}/src/Generating/VerticalStrategy.h + + ${CMAKE_SOURCE_DIR}/src/Noise/Noise.h + + ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/Event.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/File.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/GZipFile.h + ${CMAKE_SOURCE_DIR}/src/OSSupport/StackTrace.h + + ${CMAKE_SOURCE_DIR}/src/WorldStorage/FastNBT.h + ${CMAKE_SOURCE_DIR}/src/WorldStorage/SchematicFileSerializer.h +) + +set (SRCS + LuaThreadStress.cpp + Stubs.cpp + LuaState_Typedefs.inc + LuaState_Declaration.inc + Bindings.h +) + + +if (MSVC) + # Add the MSVC-specific LeakFinder sources: + list (APPEND SHARED_SRCS ${CMAKE_SOURCE_DIR}/src/LeakFinder.cpp ${CMAKE_SOURCE_DIR}/src/StackWalker.cpp) + list (APPEND SHARED_HDRS ${CMAKE_SOURCE_DIR}/src/LeakFinder.h ${CMAKE_SOURCE_DIR}/src/StackWalker.h) +endif() + +source_group("Shared" FILES ${SHARED_SRCS} ${SHARED_HDRS}) +source_group("Sources" FILES ${SRCS}) +source_group("Lua files" FILES Test.lua) +add_executable(LuaThreadStress ${SRCS} ${SHARED_SRCS} ${SHARED_HDRS} Test.lua) +target_link_libraries(LuaThreadStress tolualib zlib) +add_test(NAME LuaThreadStress-test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND LuaThreadStress) + + + + +# Put the projects into solution folders (MSVC): +set_target_properties( + LuaThreadStress + PROPERTIES FOLDER Tests +) diff --git a/tests/LuaThreadStress/LuaState_Declaration.inc b/tests/LuaThreadStress/LuaState_Declaration.inc new file mode 100644 index 000000000..4019b26c6 --- /dev/null +++ b/tests/LuaThreadStress/LuaState_Declaration.inc @@ -0,0 +1,4 @@ + +// LuaState_Declaration.inc + +// Dummy include file needed for LuaState to compile successfully diff --git a/tests/LuaThreadStress/LuaState_Typedefs.inc b/tests/LuaThreadStress/LuaState_Typedefs.inc new file mode 100644 index 000000000..5eba7c6f8 --- /dev/null +++ b/tests/LuaThreadStress/LuaState_Typedefs.inc @@ -0,0 +1,19 @@ + +// LuaState_Typedefs.inc + +// Dummy include file needed for LuaState to compile successfully + + + + + +// Forward-declare classes that are used in the API but never called: +struct HTTPRequest; +struct HTTPTemplateRequest; +class cPluginLua; +class cBoundingBox; +template class cItemCallback; +class cEntity; + + + diff --git a/tests/LuaThreadStress/LuaThreadStress.cpp b/tests/LuaThreadStress/LuaThreadStress.cpp new file mode 100644 index 000000000..f4d782ddc --- /dev/null +++ b/tests/LuaThreadStress/LuaThreadStress.cpp @@ -0,0 +1,154 @@ +// 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; +} diff --git a/tests/LuaThreadStress/Stubs.cpp b/tests/LuaThreadStress/Stubs.cpp new file mode 100644 index 000000000..717b5679c --- /dev/null +++ b/tests/LuaThreadStress/Stubs.cpp @@ -0,0 +1,273 @@ + +// Stubs.cpp + +// Implements stubs of various Cuberite methods that are needed for linking but not for runtime +// This is required so that we don't bring in the entire Cuberite via dependencies + +#include "Globals.h" +#include "BlockInfo.h" +#include "Bindings.h" +#include "Bindings/DeprecatedBindings.h" +#include "Bindings/LuaJson.h" +#include "Bindings/ManualBindings.h" +#include "BlockEntities/BlockEntity.h" +#include "Blocks/BlockHandler.h" +#include "Generating/ChunkDesc.h" + + + + + +// fwd: +struct lua_State; + + + + + +// Prototypes, needed by clang: +extern "C" int luaopen_lsqlite3(lua_State * a_LuaState); +extern "C" int luaopen_lxp(lua_State * a_LuaState); + + + + + +void cManualBindings::Bind(lua_State * a_LuaState) +{ +} + + + + + +void DeprecatedBindings::Bind(lua_State * a_LuaState) +{ +} + + + + + +void cLuaJson::Bind(cLuaState & a_LuaState) +{ +} + + + + + +int tolua_AllToLua_open(lua_State * a_LuaState) +{ + return 0; +} + + + + + +extern "C" int luaopen_lsqlite3(lua_State * a_LuaState) +{ + return 0; +} + + + + + +extern "C" int luaopen_lxp(lua_State * a_LuaState) +{ + return 0; +} + + + + + +cBlockInfo::~cBlockInfo() +{ +} + + + + + +void cBlockInfo::Initialize(cBlockInfo::cBlockInfoArray & a_BlockInfos) +{ + // The piece-loading code uses the handlers for rotations, so we need valid handlers + // Insert dummy handlers: + for (size_t i = 0; i < ARRAYCOUNT(a_BlockInfos); i++) + { + a_BlockInfos[i].m_Handler = new cBlockHandler(static_cast(i)); + } +} + + + + + +cBlockHandler::cBlockHandler(BLOCKTYPE a_BlockType) +{ +} + + + + + +bool cBlockHandler::GetPlacementBlockTypeMeta( + cChunkInterface & a_ChunkInterface, cPlayer * a_Player, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, + int a_CursorX, int a_CursorY, int a_CursorZ, + BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta +) +{ + return true; +} + + + + + +void cBlockHandler::OnUpdate(cChunkInterface & cChunkInterface, cWorldInterface & a_WorldInterface, cBlockPluginInterface & a_PluginInterface, cChunk & a_Chunk, int a_BlockX, int a_BlockY, int a_BlockZ) +{ +} + + + + + +void cBlockHandler::OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, const sSetBlock & a_BlockChange) +{ +} + + + + + +void cBlockHandler::OnDestroyedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) +{ +} + + + + + +void cBlockHandler::OnPlaced(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +{ +} + + + + + +void cBlockHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ) +{ +} + + + + + +void cBlockHandler::NeighborChanged(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_WhichNeighbor) +{ +} + + + + + +void cBlockHandler::ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) +{ +} + + + + + +void cBlockHandler::DropBlock(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cBlockPluginInterface & a_BlockPluginInterface, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, bool a_CanDrop) +{ +} + + + + + +bool cBlockHandler::CanBeAt(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ, const cChunk & a_Chunk) +{ + return true; +} + + + + + +bool cBlockHandler::IsUseable() +{ + return false; +} + + + + + +bool cBlockHandler::IsClickedThrough(void) +{ + return false; +} + + + + + +bool cBlockHandler::DoesIgnoreBuildCollision(void) +{ + return (m_BlockType == E_BLOCK_AIR); +} + + + + + +bool cBlockHandler::DoesDropOnUnsuitable(void) +{ + return true; +} + + + + + +void cBlockHandler::Check(cChunkInterface & a_ChunkInterface, cBlockPluginInterface & a_PluginInterface, int a_RelX, int a_RelY, int a_RelZ, cChunk & a_Chunk) +{ +} + + + + + +ColourID cBlockHandler::GetMapBaseColourID(NIBBLETYPE a_Meta) +{ + return 0; +} + + + + + +bool cBlockHandler::IsInsideBlock(const Vector3d & a_Position, const BLOCKTYPE a_BlockType, const NIBBLETYPE a_BlockMeta) +{ + return true; +} + + + + + +cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World) +{ + return nullptr; +} + + + + diff --git a/tests/LuaThreadStress/Test.lua b/tests/LuaThreadStress/Test.lua new file mode 100644 index 000000000..6053640b4 --- /dev/null +++ b/tests/LuaThreadStress/Test.lua @@ -0,0 +1,17 @@ +-- Test.lua + +-- Implements the test support functions +-- This file is loaded into the cLuaState used for stress-testing + + + + + +--- Returns a function that the C++ code can call +-- The callback takes a single number as param and returns the sum of the param and the seed, given to this factory function (for verification) +function getCallback(a_Seed) + return function (a_Param) + -- print("Callback " .. a_Seed .. " called with param " .. a_Param) + return a_Param + a_Seed + end +end -- cgit v1.2.3