mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-19 08:09:12 +01:00
Fix incorrect transaction batching during conversation delete.
This commit is contained in:
committed by
Greyson Parrelli
parent
7fbcd17759
commit
e23d575460
@@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.revealable.ViewOnceUtil
|
||||
import org.thoughtcrime.securesms.sms.GroupV2UpdateMessageUtil
|
||||
import org.thoughtcrime.securesms.stories.Stories.isFeatureEnabled
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.thoughtcrime.securesms.util.SignalTrace
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.MessageConstraintsUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
@@ -228,6 +229,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
const val QUOTE_TARGET_MISSING_ID = -1L
|
||||
|
||||
const val ADDRESSABLE_MESSAGE_LIMIT = 5
|
||||
private const val DELETE_BATCH_SIZE = 1000
|
||||
const val PARENT_STORY_MISSING_ID = -1L
|
||||
|
||||
const val PIN_FOREVER = Long.MAX_VALUE
|
||||
@@ -3972,14 +3974,12 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return 0
|
||||
}
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
SignalDatabase.messageSearch.dropAfterMessageDeleteTrigger()
|
||||
SignalDatabase.messageLog.dropAfterMessageDeleteTrigger()
|
||||
|
||||
for (threadId in threadsWithPossibleDeletes) {
|
||||
val subSelect = "SELECT ${TABLE_NAME}.$ID FROM $TABLE_NAME WHERE ${TABLE_NAME}.$THREAD_ID = $threadId $extraWhere LIMIT 1000"
|
||||
do {
|
||||
// Bulk deleting FK tables for large message delete efficiency
|
||||
SignalTrace.beginSection("MessageTable#deleteMessagesInThread")
|
||||
for (threadId in threadsWithPossibleDeletes) {
|
||||
val subSelect = "SELECT ${TABLE_NAME}.$ID FROM $TABLE_NAME WHERE ${TABLE_NAME}.$THREAD_ID = $threadId $extraWhere LIMIT $DELETE_BATCH_SIZE"
|
||||
var deletedCount: Int
|
||||
do {
|
||||
deletedCount = writableDatabase.withinTransaction { db ->
|
||||
db.delete(StorySendTable.TABLE_NAME)
|
||||
.where("${StorySendTable.TABLE_NAME}.${StorySendTable.MESSAGE_ID} IN ($subSelect)")
|
||||
.run()
|
||||
@@ -3992,23 +3992,28 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
.where("${CallTable.TABLE_NAME}.${CallTable.MESSAGE_ID} IN ($subSelect)")
|
||||
.run()
|
||||
|
||||
// Must delete rows from FTS table before deleting from main table due to FTS requirement when deleting by rowid
|
||||
db.delete(SearchTable.FTS_TABLE_NAME)
|
||||
.where("${SearchTable.FTS_TABLE_NAME}.${SearchTable.ID} IN ($subSelect)")
|
||||
db.delete(AttachmentTable.TABLE_NAME)
|
||||
.where("${AttachmentTable.TABLE_NAME}.${AttachmentTable.MESSAGE_ID} IN ($subSelect)")
|
||||
.run()
|
||||
|
||||
// Actually delete messages
|
||||
val deletedCount = db.delete(TABLE_NAME)
|
||||
db.delete(GroupReceiptTable.TABLE_NAME)
|
||||
.where("${GroupReceiptTable.TABLE_NAME}.${GroupReceiptTable.MMS_ID} IN ($subSelect)")
|
||||
.run()
|
||||
|
||||
db.delete(MentionTable.TABLE_NAME)
|
||||
.where("${MentionTable.TABLE_NAME}.${MentionTable.MESSAGE_ID} IN ($subSelect)")
|
||||
.run()
|
||||
|
||||
// Delete the messages themselves
|
||||
db.delete(TABLE_NAME)
|
||||
.where("$ID IN ($subSelect)")
|
||||
.run()
|
||||
}
|
||||
|
||||
totalDeletedCount += deletedCount
|
||||
} while (deletedCount > 0)
|
||||
}
|
||||
|
||||
SignalDatabase.messageSearch.restoreAfterMessageDeleteTrigger()
|
||||
SignalDatabase.messageLog.restoreAfterMessageDeleteTrigger()
|
||||
totalDeletedCount += deletedCount
|
||||
} while (deletedCount > 0)
|
||||
}
|
||||
SignalTrace.endSection()
|
||||
|
||||
return totalDeletedCount
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||
import org.thoughtcrime.securesms.util.SignalTrace
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.thoughtcrime.securesms.util.JsonUtils.SaneJSONObject
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
@@ -1315,46 +1316,43 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
}
|
||||
|
||||
fun deleteConversations(selectedConversations: Set<Long>, syncThreadDeletes: Boolean = true) {
|
||||
SignalTrace.beginSection("ThreadTable#deleteConversations")
|
||||
Log.d(TAG, "[deleteConversations] Deleting ${selectedConversations.size} chats syncThreadDeletes: $syncThreadDeletes")
|
||||
val recipientIds = getRecipientIdsForThreadIds(selectedConversations)
|
||||
|
||||
val addressableMessages = mutableListOf<ThreadDeleteSyncInfo>()
|
||||
|
||||
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(ID, selectedConversations)
|
||||
Log.d(TAG, "[deleteConversations] Enter transaction")
|
||||
writableDatabase.withinTransaction { db ->
|
||||
if (syncThreadDeletes) {
|
||||
for (threadId in selectedConversations) {
|
||||
val mostRecentMessages = messages.getMostRecentAddressableMessages(threadId, excludeExpiring = false)
|
||||
val mostRecentNonExpiring = if (mostRecentMessages.size == MessageTable.ADDRESSABLE_MESSAGE_LIMIT && mostRecentMessages.any { it.expiresIn > 0 }) {
|
||||
messages.getMostRecentAddressableMessages(threadId, excludeExpiring = true)
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
|
||||
addressableMessages += ThreadDeleteSyncInfo(threadId, mostRecentMessages, mostRecentNonExpiring)
|
||||
// Phase 1: Collect sync info (reads only, before any deletion)
|
||||
if (syncThreadDeletes) {
|
||||
for (threadId in selectedConversations) {
|
||||
val mostRecentMessages = messages.getMostRecentAddressableMessages(threadId, excludeExpiring = false)
|
||||
val mostRecentNonExpiring = if (mostRecentMessages.size == MessageTable.ADDRESSABLE_MESSAGE_LIMIT && mostRecentMessages.any { it.expiresIn > 0 }) {
|
||||
messages.getMostRecentAddressableMessages(threadId, excludeExpiring = true)
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
Log.d(TAG, "[deleteConversations] Retrieved sync thread delete addressable messages (${addressableMessages.size})")
|
||||
} else {
|
||||
Log.d(TAG, "[deleteConversations] No addressable messages needed")
|
||||
}
|
||||
|
||||
Log.d(TAG, "[deleteConversations] Deactivating threads")
|
||||
addressableMessages += ThreadDeleteSyncInfo(threadId, mostRecentMessages, mostRecentNonExpiring)
|
||||
}
|
||||
Log.d(TAG, "[deleteConversations] Retrieved sync thread delete addressable messages (${addressableMessages.size})")
|
||||
} else {
|
||||
Log.d(TAG, "[deleteConversations] No addressable messages needed")
|
||||
}
|
||||
|
||||
// Phase 2: Delete messages (per-batch transactions, write lock released between batches)
|
||||
Log.d(TAG, "[deleteConversations] Deleting messages in thread")
|
||||
messages.deleteMessagesInThread(selectedConversations)
|
||||
|
||||
// Phase 3: Final lightweight transaction (deactivate threads, clear drafts, update cache)
|
||||
val queries: List<SqlUtil.Query> = SqlUtil.buildCollectionQuery(ID, selectedConversations)
|
||||
Log.d(TAG, "[deleteConversations] Deactivating threads and cleaning up")
|
||||
writableDatabase.withinTransaction { db ->
|
||||
for (query in queries) {
|
||||
db.deactivateThread(query)
|
||||
}
|
||||
|
||||
Log.d(TAG, "[deleteConversations] Deleting messages in thread")
|
||||
messages.deleteMessagesInThread(selectedConversations)
|
||||
Log.d(TAG, "[deleteConversations] Trimming attachments")
|
||||
attachments.trimAllAbandonedAttachments()
|
||||
Log.d(TAG, "[deleteConversations] Deleting abandoned group receipts")
|
||||
groupReceipts.deleteAbandonedRows()
|
||||
Log.d(TAG, "[deleteConversations] Deleting abandoned mentions")
|
||||
mentions.deleteAbandonedMentions()
|
||||
Log.d(TAG, "[deleteConversations] Clearing drafts")
|
||||
drafts.clearDrafts(selectedConversations)
|
||||
Log.d(TAG, "[deleteConversations] Updating threadId cache")
|
||||
|
||||
synchronized(threadIdCache) {
|
||||
for (recipientId in recipientIds) {
|
||||
threadIdCache.remove(recipientId)
|
||||
@@ -1378,6 +1376,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
ConversationUtil.clearShortcuts(context, recipientIds)
|
||||
|
||||
OptimizeMessageSearchIndexJob.enqueue()
|
||||
SignalTrace.endSection()
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
|
||||
Reference in New Issue
Block a user