From 0e953640cd3f9ef8d40a0179504cca1552d76d8c Mon Sep 17 00:00:00 2001 From: Fredy Date: Sun, 28 Nov 2021 05:49:40 +0100 Subject: [PATCH] Added experimental management of active database instances using weak tables --- src/lua/GMModule.cpp | 23 +++++++++--- src/lua/LuaDatabase.cpp | 83 +++++++++++++++++++++++++++++++++++++++-- src/lua/LuaDatabase.h | 8 +--- src/lua/LuaObject.cpp | 12 ------ src/lua/LuaObject.h | 3 +- 5 files changed, 99 insertions(+), 30 deletions(-) diff --git a/src/lua/GMModule.cpp b/src/lua/GMModule.cpp index d1d4c4d..02d998f 100644 --- a/src/lua/GMModule.cpp +++ b/src/lua/GMModule.cpp @@ -18,10 +18,8 @@ GMOD_MODULE_CLOSE() { // Free the version check ConVar object reference if (versionCheckConVar != 0) { LUA->ReferenceFree(versionCheckConVar); + versionCheckConVar = 0; } - - delete LuaDatabase::luaDatabases; - LuaDatabase::luaDatabases = nullptr; mysql_thread_end(); mysql_library_end(); @@ -136,11 +134,22 @@ LUA_FUNCTION(deallocationCount) { return 1; } +LUA_FUNCTION(mysqlooThink) { + LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); + LUA->GetField(-1, "mysqloo"); + if (LUA->IsType(-1, GarrysMod::Lua::Type::Nil)) { + LUA->Pop(2); //nil, Global + return 0; + } + LuaDatabase::runAllThinkHooks(LUA); + LUA->Pop(2); //nil, Global + return 0; +} + GMOD_MODULE_OPEN() { if (mysql_library_init(0, nullptr, nullptr)) { LUA->ThrowError("Could not initialize mysql library."); } - LuaDatabase::luaDatabases = new std::unordered_set(); //Creating MetaTables LuaObject::createUserDataMetaTable(LUA); @@ -154,12 +163,12 @@ GMOD_MODULE_OPEN() { LUA->GetField(-1, "Add"); LUA->PushString("Think"); LUA->PushString("__MySQLOOThinkHook"); - LUA->PushCFunction(LuaObject::luaObjectThink); + LUA->PushCFunction(mysqlooThink); LUA->Call(3, 0); LUA->Pop(); LUA->Pop(); LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); - LUA->CreateTable(); + LUA->CreateTable(); //mysqloo LUA->PushString(MYSQLOO_VERSION); LUA->SetField(-2, "VERSION"); @@ -206,6 +215,8 @@ GMOD_MODULE_OPEN() { LUA->PushCFunction(deallocationCount); LUA->SetField(-2, "deallocationCount"); + LuaDatabase::createWeakTable(LUA); + LUA->SetField(-2, "mysqloo"); LUA->Pop(); diff --git a/src/lua/LuaDatabase.cpp b/src/lua/LuaDatabase.cpp index d6a4f2f..5a7ad02 100644 --- a/src/lua/LuaDatabase.cpp +++ b/src/lua/LuaDatabase.cpp @@ -4,8 +4,6 @@ #include "LuaPreparedQuery.h" #include "LuaTransaction.h" -std::unordered_set* LuaDatabase::luaDatabases = nullptr; - static void pushLuaObjectTable(ILuaBase *LUA, void *data, int type) { LUA->CreateTable(); LUA->PushUserType(data, LuaObject::TYPE_USERDATA); @@ -35,6 +33,21 @@ LUA_CLASS_FUNCTION(LuaDatabase, create) { auto luaDatabase = new LuaDatabase(createdDatabase); pushLuaObjectTable(LUA, luaDatabase, LuaObject::TYPE_DATABASE); + int databaseTablePos = LUA->Top(); + + //Add database to weak database table + LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); + LUA->GetField(-1, "mysqloo"); + LUA->GetField(-1, "__weakDatabases"); + if (LUA->IsType(-1, GarrysMod::Lua::Type::Nil)) { + LUA->Pop(3); //nil, mysqloo, Global + return 1; + } + LUA->Push(databaseTablePos); + LUA->PushBool(true); + LUA->RawSet(-3); + LUA->Pop(3); //__weakDatabases, mysqloo, Global + return 1; } @@ -196,7 +209,7 @@ MYSQLOO_LUA_FUNCTION(setCachePreparedStatements) { MYSQLOO_LUA_FUNCTION(abortAllQueries) { auto database = LuaObject::getLuaObject(LUA); auto abortedQueries = database->m_database->abortAllQueries(); - for (const auto& pair: abortedQueries) { + for (const auto &pair: abortedQueries) { LuaIQuery::runAbortedCallback(LUA, pair.second); LuaIQuery::finishQueryData(LUA, pair.first, pair.second); } @@ -328,5 +341,67 @@ void LuaDatabase::onDestroyedByLua(ILuaBase *LUA) { //This needs to be cleared to avoid the queries leaking m_database->takeFinishedQueries(); m_database->abortAllQueries(); +} -} \ No newline at end of file +/** Creates a weak table for all currently used databases. + * And stores it in the table at the top of the stack at key "__weakDatabases" + * + * Expects the mysqloo table to be at the top of the stack. + * + * See runAllThinkHooks for why this is used. + */ +void LuaDatabase::createWeakTable(ILuaBase *LUA) { + //Weak metatable + LUA->CreateTable(); + LUA->PushString("k"); + LUA->SetField(-2, "__mode"); + + //Weak table + LUA->CreateTable(); + LUA->Push(-2); //Metatable + LUA->SetMetaTable(-2); + + LUA->SetField(-3, "__weakDatabases"); + LUA->Pop(); //Metatable +} + +/** + * Runs the think hook for every database instance that is currently alive. + * Expects the mysqloo table to be at the top of the stack. + * + * The idea of this function is to store a weak reference to each database's lua table and then + * call the think function of each instance that is still alive. + * If the table of a database instance is still alive, then so is the UserData object (which is stored in the table). + * + * This method eliminates the need of storing a global list of all database instances that are currently alive. + */ +void LuaDatabase::runAllThinkHooks(ILuaBase *LUA) { + LUA->GetField(-1, "__weakDatabases"); + if (!LUA->IsType(-1, GarrysMod::Lua::Type::Table)) { + LUA->Pop(); //Nil + return; + } + //We iterate all (alive) entries in the weak table and create references to them. + //This essentially creates a copy of the weak table that only contains strong references. + //We need the copy so that no elements of the weak table are collected while think hooks of the databases + //are executed, which might create new database instances, thus invalidating the iterator. + std::vector databaseReferences; + LUA->PushNil(); //First key + while (LUA->Next(-2) != 0) { + //The key is the table of the database + LUA->Push(-2); //The key, i.e. the database table + databaseReferences.push_back(LUA->ReferenceCreate()); + + LUA->Pop(); //The value, keep key on stack for next() + } + LUA->Pop(); //__weakDatabases + + //Call think function of each alive database instance + for (auto &ref: databaseReferences) { + LUA->ReferencePush(ref); + LUA->ReferenceFree(ref); //We can immediately free this, the variable on the stack keeps it alive. + auto database = LuaObject::getLuaObject(LUA, -1); + database->think(LUA); + LUA->Pop(); //database + } +} diff --git a/src/lua/LuaDatabase.h b/src/lua/LuaDatabase.h index 8b693d5..d06062d 100644 --- a/src/lua/LuaDatabase.h +++ b/src/lua/LuaDatabase.h @@ -22,16 +22,12 @@ class LuaDatabase : public LuaObject { void onDestroyedByLua(ILuaBase *LUA) override; - ~LuaDatabase() override { - luaDatabases->erase(this); - } - explicit LuaDatabase(std::shared_ptr database) : LuaObject("Database"), m_database(std::move(database)) { - luaDatabases->insert(this); } - static std::unordered_set* luaDatabases; + static void createWeakTable(ILuaBase *LUA); + static void runAllThinkHooks(ILuaBase *LUA); }; diff --git a/src/lua/LuaObject.cpp b/src/lua/LuaObject.cpp index a674e90..3d2c8cd 100644 --- a/src/lua/LuaObject.cpp +++ b/src/lua/LuaObject.cpp @@ -25,18 +25,6 @@ LUA_FUNCTION(luaObjectGc) { return 0; } -LUA_CLASS_FUNCTION(LuaObject, luaObjectThink) { - std::unordered_set databasesCopy = *LuaDatabase::luaDatabases; - for (auto &database: databasesCopy) { - if (LuaDatabase::luaDatabases->find(database) == LuaDatabase::luaDatabases->end()) { - //This means the database instance was collected during the think hook and is thus invalid. - continue; - } - database->think(LUA); - } - return 0; -} - void LuaObject::createUserDataMetaTable(GarrysMod::Lua::ILuaBase *LUA) { TYPE_USERDATA = LUA->CreateMetaTable("MySQLOO UserData"); LUA->PushCFunction(luaObjectGc); diff --git a/src/lua/LuaObject.h b/src/lua/LuaObject.h index 5387646..0026dc1 100644 --- a/src/lua/LuaObject.h +++ b/src/lua/LuaObject.h @@ -42,7 +42,6 @@ class LuaObject { static void createUserDataMetaTable(ILuaBase *lua); //Lua functions - static int luaObjectThink(lua_State *L); static void pcallWithErrorReporter(ILuaBase *LUA, int nargs); @@ -62,7 +61,7 @@ class LuaObject { if (returnValue == nullptr) { LUA->ThrowError("[MySQLOO] Invalid CPP Object"); } - LUA->Pop(); + LUA->Pop(); //__CppObject return returnValue; }