From 3cd238616add4d476042a4c6761c3d42ee770fa3 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 3 Dec 2025 17:07:15 -0800 Subject: [PATCH 1/4] [FCM] Recovery logic for a corrupt database --- .../Sources/FIRMessagingRmqManager.m | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/FirebaseMessaging/Sources/FIRMessagingRmqManager.m b/FirebaseMessaging/Sources/FIRMessagingRmqManager.m index 6a28d1d926d..903047933fd 100644 --- a/FirebaseMessaging/Sources/FIRMessagingRmqManager.m +++ b/FirebaseMessaging/Sources/FIRMessagingRmqManager.m @@ -501,7 +501,7 @@ - (void)openDatabase { #ifdef SQLITE_OPEN_FILEPROTECTION_NONE flags |= SQLITE_OPEN_FILEPROTECTION_NONE; #endif - int result = sqlite3_open_v2([path UTF8String], &self -> _database, flags, NULL); + int result = sqlite3_open_v2([path UTF8String], &self->_database, flags, NULL); if (result != SQLITE_OK) { NSString *errorString = FIRMessagingStringFromSQLiteResult(result); NSString *errorMessage = [NSString @@ -517,21 +517,41 @@ - (void)openDatabase { [self createTableWithName:kTableLastRmqId command:kCreateTableLastRmqId]; [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds]; } else { - // Calling sqlite3_open should create the database, since the file doesn't exist. + // The file exists, try to open it. If it fails, it might be corrupt. int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; #ifdef SQLITE_OPEN_FILEPROTECTION_NONE flags |= SQLITE_OPEN_FILEPROTECTION_NONE; #endif - int result = sqlite3_open_v2([path UTF8String], &self -> _database, flags, NULL); + int result = sqlite3_open_v2([path UTF8String], &self->_database, flags, NULL); + + // If opening the database failed, it might be corrupt. Try to recover by deleting and + // recreating it. if (result != SQLITE_OK) { - NSString *errorString = FIRMessagingStringFromSQLiteResult(result); - NSString *errorMessage = - [NSString stringWithFormat:@"Could not create RMQ database at path %@, error: %@", path, - errorString]; - FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorCreatingDatabase, - @"%@", errorMessage); - NSAssert(NO, errorMessage); - didOpenDatabase = NO; + FIRMessagingLoggerWarn(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase, + @"Could not open RMQ database at path: %@. " + "Will delete and try to recreate it.", + path); + [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; + // After deleting, try to open it again. + result = sqlite3_open_v2([path UTF8String], &self->_database, flags, NULL); + // If it still fails after the recovery attempt, then assert and crash. + if (result != SQLITE_OK) { + NSString *errorString = FIRMessagingStringFromSQLiteResult(result); + NSString *errorMessage = [NSString + stringWithFormat:@"Could not open or create RMQ database at path %@, error: %@", path, + errorString]; + FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase, + @"%@", errorMessage); + NSAssert(NO, errorMessage); + didOpenDatabase = NO; // Still failed, so indicate database did not open. + } else { + // Successfully recreated after corruption, so treat as a new database for table creation. + didOpenDatabase = YES; // Indicate successful opening after recreation. + [self createTableWithName:kTableOutgoingRmqMessages + command:kCreateTableOutgoingRmqMessages]; + [self createTableWithName:kTableLastRmqId command:kCreateTableLastRmqId]; + [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds]; + } } else { [self updateDBWithStringRmqID]; } From 21db2f6c9b4ce169ebeac366cd7fca7ad8e926bb Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:03:36 -0500 Subject: [PATCH 2/4] Update FirebaseMessaging/Sources/FIRMessagingRmqManager.m Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- FirebaseMessaging/Sources/FIRMessagingRmqManager.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FirebaseMessaging/Sources/FIRMessagingRmqManager.m b/FirebaseMessaging/Sources/FIRMessagingRmqManager.m index 903047933fd..e7d26a7b50d 100644 --- a/FirebaseMessaging/Sources/FIRMessagingRmqManager.m +++ b/FirebaseMessaging/Sources/FIRMessagingRmqManager.m @@ -531,7 +531,10 @@ - (void)openDatabase { @"Could not open RMQ database at path: %@. " "Will delete and try to recreate it.", path); - [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; +NSError *removeError; +if (![[NSFileManager defaultManager] removeItemAtPath:path error:&removeError]) { + FIRMessagingLoggerWarn(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase, @"Failed to delete corrupt database at %@: %@", path, removeError); +} // After deleting, try to open it again. result = sqlite3_open_v2([path UTF8String], &self->_database, flags, NULL); // If it still fails after the recovery attempt, then assert and crash. From 3d990003186362bf603f446b7d01eeaeee797d45 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 6 Jan 2026 14:06:29 -0500 Subject: [PATCH 3/4] changelog and style --- FirebaseMessaging/CHANGELOG.md | 3 +++ FirebaseMessaging/Sources/FIRMessagingRmqManager.m | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/FirebaseMessaging/CHANGELOG.md b/FirebaseMessaging/CHANGELOG.md index f2756c5ce0a..9f2346bc2d3 100644 --- a/FirebaseMessaging/CHANGELOG.md +++ b/FirebaseMessaging/CHANGELOG.md @@ -1,3 +1,6 @@ +# 12.8.0 +- [fixed] Fix missing database crash on launch. (#14880) + # 12.1.0 - [fixed] Fix Xcode 26 crash from missing `NSUserActivityTypeBrowsingWeb` symbol. Note that this fix isn't in the 12.1.0 zip and Carthage diff --git a/FirebaseMessaging/Sources/FIRMessagingRmqManager.m b/FirebaseMessaging/Sources/FIRMessagingRmqManager.m index e7d26a7b50d..39f822ce138 100644 --- a/FirebaseMessaging/Sources/FIRMessagingRmqManager.m +++ b/FirebaseMessaging/Sources/FIRMessagingRmqManager.m @@ -531,10 +531,11 @@ - (void)openDatabase { @"Could not open RMQ database at path: %@. " "Will delete and try to recreate it.", path); -NSError *removeError; -if (![[NSFileManager defaultManager] removeItemAtPath:path error:&removeError]) { - FIRMessagingLoggerWarn(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase, @"Failed to delete corrupt database at %@: %@", path, removeError); -} + NSError *removeError; + if (![[NSFileManager defaultManager] removeItemAtPath:path error:&removeError]) { + FIRMessagingLoggerWarn(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase, + @"Failed to delete corrupt database at %@: %@", path, removeError); + } // After deleting, try to open it again. result = sqlite3_open_v2([path UTF8String], &self->_database, flags, NULL); // If it still fails after the recovery attempt, then assert and crash. From 778872df9a523d29e92954d9f5b3814e10ae2237 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 6 Jan 2026 14:11:21 -0500 Subject: [PATCH 4/4] refactor out table creation --- .../Sources/FIRMessagingRmqManager.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/FirebaseMessaging/Sources/FIRMessagingRmqManager.m b/FirebaseMessaging/Sources/FIRMessagingRmqManager.m index 39f822ce138..ed01c0ad26f 100644 --- a/FirebaseMessaging/Sources/FIRMessagingRmqManager.m +++ b/FirebaseMessaging/Sources/FIRMessagingRmqManager.m @@ -489,6 +489,12 @@ - (void)removeDatabase { }); } +- (void)createTable { + [self createTableWithName:kTableOutgoingRmqMessages command:kCreateTableOutgoingRmqMessages]; + [self createTableWithName:kTableLastRmqId command:kCreateTableLastRmqId]; + [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds]; +} + - (void)openDatabase { dispatch_async(_databaseOperationQueue, ^{ NSFileManager *fileManager = [NSFileManager defaultManager]; @@ -512,10 +518,7 @@ - (void)openDatabase { NSAssert(NO, errorMessage); return; } - [self createTableWithName:kTableOutgoingRmqMessages command:kCreateTableOutgoingRmqMessages]; - - [self createTableWithName:kTableLastRmqId command:kCreateTableLastRmqId]; - [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds]; + [self createTable]; } else { // The file exists, try to open it. If it fails, it might be corrupt. int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; @@ -551,10 +554,7 @@ - (void)openDatabase { } else { // Successfully recreated after corruption, so treat as a new database for table creation. didOpenDatabase = YES; // Indicate successful opening after recreation. - [self createTableWithName:kTableOutgoingRmqMessages - command:kCreateTableOutgoingRmqMessages]; - [self createTableWithName:kTableLastRmqId command:kCreateTableLastRmqId]; - [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds]; + [self createTable]; } } else { [self updateDBWithStringRmqID];