summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Bindings/LuaState.cpp12
-rw-r--r--src/Bindings/LuaState.h3
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/LuaThreadStress/Bindings.h15
-rw-r--r--tests/LuaThreadStress/CMakeLists.txt97
-rw-r--r--tests/LuaThreadStress/LuaState_Declaration.inc4
-rw-r--r--tests/LuaThreadStress/LuaState_Typedefs.inc19
-rw-r--r--tests/LuaThreadStress/LuaThreadStress.cpp154
-rw-r--r--tests/LuaThreadStress/Stubs.cpp273
-rw-r--r--tests/LuaThreadStress/Test.lua17
10 files changed, 589 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);
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 <typename T> 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 <thread>
+#include <random>
+
+
+
+
+
+/** 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<bool> * a_ShouldTerminate, std::atomic<int> * a_FailResult)
+{
+ std::minstd_rand rnd;
+ rnd.seed(a_Seed);
+ auto callbackSeed = static_cast<unsigned>(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<unsigned>(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<bool> shouldTerminate(false);
+ std::atomic<int> 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<BLOCKTYPE>(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