1
0

Merge pull request #3537 from cuberite/LuaStressTest

Lua stress test
This commit is contained in:
Mattes D 2017-01-15 16:10:06 +01:00 committed by GitHub
commit fa69d09a49
10 changed files with 589 additions and 6 deletions

View File

@ -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;
}
));
}
}

View File

@ -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);

View File

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

View File

@ -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);

View File

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

View File

@ -0,0 +1,4 @@
// LuaState_Declaration.inc
// Dummy include file needed for LuaState to compile successfully

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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