diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.h b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.h --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.h +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.h @@ -12,6 +12,10 @@ static const int backupDataKeySize; static const int backupLogDataKeySize; + // Connection manager instance, should be only one (globally) to each + // database. + static std::shared_ptr connectionManager; + // Indicate that at least one instance of SQLiteQueryExecutor was created, // which is identical to finishing the migration process and having a fully // operational database that can be used by application logic. diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp @@ -20,6 +20,9 @@ const int DatabaseManager::backupDataKeySize = 64; const int DatabaseManager::backupLogDataKeySize = 32; +std::shared_ptr + DatabaseManager::connectionManager; + std::once_flag DatabaseManager::queryExecutorCreationIndicated; std::once_flag DatabaseManager::sqliteQueryExecutorPropertiesInitialized; @@ -32,7 +35,7 @@ const std::string DATABASE_MANAGER_STATUS_KEY = "DATABASE_MANAGER_STATUS"; const DatabaseQueryExecutor &DatabaseManager::getQueryExecutor() { - thread_local SQLiteQueryExecutor instance; + thread_local SQLiteQueryExecutor instance(DatabaseManager::connectionManager); // creating an instance means that migration code was executed // and finished without error and database is workable @@ -50,7 +53,7 @@ std::string backupDataKey = DatabaseManager::generateBackupDataKey(); std::string backupLogDataKey = DatabaseManager::generateBackupLogDataKey(); - SQLiteQueryExecutor::connectionManager.closeConnection(); + DatabaseManager::connectionManager->closeConnection(); if (SQLiteUtils::fileExists(SQLiteQueryExecutor::sqliteFilePath) && std::remove(SQLiteQueryExecutor::sqliteFilePath.c_str())) { std::ostringstream errorStream; @@ -71,6 +74,8 @@ void DatabaseManager::initializeQueryExecutor(std::string &databasePath) { try { + DatabaseManager::connectionManager = + std::make_shared(); DatabaseManager::initializeSQLiteQueryExecutorProperties(databasePath); DatabaseManager::getQueryExecutor(); DatabaseManager::indicateQueryExecutorCreation(); @@ -191,8 +196,8 @@ throw std::runtime_error("invalid backupLogDataKey size"); } - thread_local SQLiteQueryExecutor instance; - SQLiteUtils::rekeyDatabase(instance.getConnection(), backupDataKey); + SQLiteUtils::rekeyDatabase( + DatabaseManager::connectionManager->getConnection(), backupDataKey); CommSecureStore::set(CommSecureStore::backupDataKey, backupDataKey); SQLiteQueryExecutor::backupDataKey = backupDataKey; @@ -216,7 +221,7 @@ logID = "1"; } - bool newLogCreated = SQLiteQueryExecutor::connectionManager.captureLogs( + bool newLogCreated = DatabaseManager::connectionManager->captureLogs( backupID, logID, SQLiteQueryExecutor::backupLogDataKey); if (!newLogCreated) { return; @@ -234,8 +239,6 @@ } void DatabaseManager::createMainCompaction(std::string backupID) { - thread_local SQLiteQueryExecutor instance; - std::string finalBackupPath = PlatformSpecificTools::getBackupFilePath(backupID, false); std::string finalAttachmentsPath = @@ -269,8 +272,11 @@ sqlite3_open(tempBackupPath.c_str(), &backupDB); SQLiteUtils::setEncryptionKey(backupDB, SQLiteQueryExecutor::backupDataKey); - sqlite3_backup *backupObj = - sqlite3_backup_init(backupDB, "main", instance.getConnection(), "main"); + sqlite3_backup *backupObj = sqlite3_backup_init( + backupDB, + "main", + DatabaseManager::connectionManager->getConnection(), + "main"); if (!backupObj) { std::stringstream error_message; error_message << "Failed to init backup for main compaction. Details: " @@ -316,7 +322,8 @@ std::string getAllBlobServiceMediaSQL = "SELECT * FROM media WHERE uri LIKE 'comm-blob-service://%';"; std::vector blobServiceMedia = getAllEntities( - instance.getConnection(), getAllBlobServiceMediaSQL); + DatabaseManager::connectionManager->getConnection(), + getAllBlobServiceMediaSQL); for (const auto &media : blobServiceMedia) { std::string blobServiceURI = media.uri; @@ -335,7 +342,7 @@ DatabaseManager::getQueryExecutor().setMetadata("backupID", backupID); DatabaseManager::getQueryExecutor().clearMetadata("logID"); if (ServicesUtils::fullBackupSupport) { - SQLiteQueryExecutor::connectionManager.setLogsMonitoring(true); + DatabaseManager::connectionManager->setLogsMonitoring(true); } } diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h @@ -40,14 +40,15 @@ static std::string backupLogDataKey; #ifndef EMSCRIPTEN - static NativeSQLiteConnectionManager connectionManager; + std::shared_ptr connectionManager; + SQLiteQueryExecutor( + std::shared_ptr connectionManager); #else - static WebSQLiteConnectionManager connectionManager; + std::shared_ptr connectionManager; + SQLiteQueryExecutor(std::string sqliteFilePath); #endif - SQLiteQueryExecutor(); ~SQLiteQueryExecutor(); - SQLiteQueryExecutor(std::string sqliteFilePath); sqlite3 *getConnection() const; void migrate() const override; diff --git a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp @@ -35,14 +35,8 @@ std::string SQLiteQueryExecutor::backupLogDataKey; -#ifndef EMSCRIPTEN -NativeSQLiteConnectionManager SQLiteQueryExecutor::connectionManager; -#else -WebSQLiteConnectionManager SQLiteQueryExecutor::connectionManager; -#endif - void SQLiteQueryExecutor::migrate() const { - SQLiteQueryExecutor::connectionManager.validateEncryption( + this->connectionManager->validateEncryption( SQLiteQueryExecutor::sqliteFilePath, SQLiteQueryExecutor::backupDataKey); std::stringstream db_path; @@ -50,7 +44,7 @@ << std::endl; Logger::log(db_path.str()); - sqlite3 *db = SQLiteQueryExecutor::connectionManager.getEphemeralConnection( + sqlite3 *db = this->connectionManager->getEphemeralConnection( SQLiteQueryExecutor::sqliteFilePath, SQLiteQueryExecutor::backupDataKey); auto db_version = SQLiteUtils::getDatabaseVersion(db); @@ -75,34 +69,54 @@ sqlite3_close(db); } -SQLiteQueryExecutor::SQLiteQueryExecutor() { - this->migrate(); #ifndef EMSCRIPTEN +SQLiteQueryExecutor::SQLiteQueryExecutor( + std::shared_ptr connectionManager) + : connectionManager(std::move(connectionManager)) { + this->migrate(); + SQLiteQueryExecutor::connectionManager->initializeConnection( + SQLiteQueryExecutor::sqliteFilePath, SQLiteQueryExecutor::backupDataKey); std::string currentBackupID = this->getMetadata("backupID"); if (!ServicesUtils::fullBackupSupport || !currentBackupID.size()) { return; } - SQLiteQueryExecutor::connectionManager.setLogsMonitoring(true); -#endif + SQLiteQueryExecutor::connectionManager->setLogsMonitoring(true); } -SQLiteQueryExecutor::SQLiteQueryExecutor(std::string sqliteFilePath) { +#else +SQLiteQueryExecutor::SQLiteQueryExecutor(std::string sqliteFilePath) + : connectionManager(std::make_shared()) { SQLiteQueryExecutor::sqliteFilePath = sqliteFilePath; this->migrate(); + SQLiteQueryExecutor::connectionManager->initializeConnection( + SQLiteQueryExecutor::sqliteFilePath, SQLiteQueryExecutor::backupDataKey); } +#endif sqlite3 *SQLiteQueryExecutor::getConnection() const { - SQLiteQueryExecutor::connectionManager.initializeConnection( + this->connectionManager->initializeConnection( SQLiteQueryExecutor::sqliteFilePath, SQLiteQueryExecutor::backupDataKey); - return SQLiteQueryExecutor::connectionManager.getConnection(); -} - -void SQLiteQueryExecutor::closeConnection() { - SQLiteQueryExecutor::connectionManager.closeConnection(); -} - + return this->connectionManager->getConnection(); +} + +// This logic is crucial for managing the connectionManager's lifecycle within +// the SQLiteQueryExecutor. For newly instantiated connectionManagers, their +// lifecycle aligns with SQLiteQueryExecutor they belong to, and they are +// disposed of simultaneously. When using an existing connectionManager (such as +// NativeSQLiteConnectionManager from a static context like DatabaseManager), +// handling the destruction is more complex. connectionManager does not get +// destructed until the application exits (static member). There is no mechanism +// to automatically detect when all thread_local instances of +// SQLiteQueryExecutor are done, as their lifecycle is tied to thread execution, +// not directly managed through code. This code ensures that all active +// connections are closed once their corresponding threads have finished. This +// is crucial to prevent leftover issues like database file locks if the +// application is stopped unexpectedly and then run again. In +// `SQLiteQueryExecutor`, when a connection is needed, we always use the +// internal `getConnection`, which lazy-initializes the connection after it was +// closed by another instance whose thread is no longer alive. SQLiteQueryExecutor::~SQLiteQueryExecutor() { - SQLiteQueryExecutor::closeConnection(); + this->connectionManager->closeConnection(); } std::string SQLiteQueryExecutor::getDraft(std::string key) const { @@ -1619,7 +1633,7 @@ void SQLiteQueryExecutor::restoreFromBackupLog( const std::vector &backupLog) const { - SQLiteQueryExecutor::connectionManager.restoreFromBackupLog(backupLog); + this->connectionManager->restoreFromBackupLog(backupLog); } } // namespace comm diff --git a/web/shared-worker/_generated/comm-query-executor.js b/web/shared-worker/_generated/comm-query-executor.js --- a/web/shared-worker/_generated/comm-query-executor.js +++ b/web/shared-worker/_generated/comm-query-executor.js @@ -114,7 +114,7 @@ V.prototype.jd=function(a){this.Fc&&(a=this.Fc(a));return a};V.prototype.tc=function(a){this.ab&&this.ab(a)};V.prototype.argPackAdvance=8;V.prototype.readValueFromPointer=lb;V.prototype.deleteObject=function(a){if(null!==a)a["delete"]()}; V.prototype.fromWireType=function(a){function b(){return this.Hb?Lb(this.Fa.qb,{Ja:this.yd,Ea:c,Ra:this,Na:a}):Lb(this.Fa.qb,{Ja:this,Ea:a})}var c=this.jd(a);if(!c)return this.tc(a),null;var d=Kb(this.Fa,c);if(void 0!==d){if(0===d.za.count.value)return d.za.Ea=c,d.za.Na=a,d.clone();d=d.clone();this.tc(a);return d}d=this.Fa.gd(c);d=Fb[d];if(!d)return b.call(this);d=this.Fb?d.Uc:d.pointerType;var f=Eb(c,this.Fa,d.Fa);return null===f?b.call(this):this.Hb?Lb(d.Fa.qb,{Ja:d,Ea:f,Ra:this,Na:a}):Lb(d.Fa.qb, {Ja:d,Ea:f})};Wb=e.UnboundTypeError=rb("UnboundTypeError");e.count_emval_handles=function(){for(var a=0,b=5;bf?-28:N.rc(d,f).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return f=hb(),d.flags|=f,0;case 5:return f=hb(),D[f+0>>1]=2,0;case 6:case 7:return 0;case 16:case 8:return-28;case 9:return E[Jc()>>2]=28,-1;default:return-28}}catch(g){if("undefined"== typeof N||!(g instanceof N.ya))throw g;return-g.Da}},V:function(a,b){try{var c=Q(a);return fb(N.stat,c.path,b)}catch(d){if("undefined"==typeof N||!(d instanceof N.ya))throw d;return-d.Da}},Q:function(a,b){try{b=-9007199254740992>b||9007199254740992>3]=BigInt(h);J[b+a+8>>3]=BigInt(280*(g+1));D[b+a+16>>1]=280;C[b+a+18>>0]=m;B(k,z,b+a+19,256);a+=280;g+=1}N.Oa(d,280* diff --git a/web/shared-worker/_generated/comm_query_executor.wasm b/web/shared-worker/_generated/comm_query_executor.wasm index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@