Fix incorrect transaction batching during conversation delete.

This commit is contained in:
Cody Henthorne
2026-03-03 09:46:39 -05:00
committed by Greyson Parrelli
parent 7fbcd17759
commit e23d575460
8 changed files with 304 additions and 60 deletions

View File

@@ -56,6 +56,13 @@ class BenchmarkCommandReceiver : BroadcastReceiver() {
BenchmarkWebSocketConnection.startWholeBatchTrace()
BenchmarkWebSocketConnection.releaseMessages()
}
"delete-thread" -> {
val pendingResult = goAsync()
Thread {
handleDeleteThread()
pendingResult.finish()
}.start()
}
else -> Log.w(TAG, "Unknown command: $command")
}
}
@@ -144,6 +151,20 @@ class BenchmarkCommandReceiver : BroadcastReceiver() {
}
}
private fun handleDeleteThread() {
val threadId = SignalDatabase.threads.getRecentConversationList(1, false, false).use { cursor ->
if (cursor.moveToFirst()) {
cursor.getLong(cursor.getColumnIndexOrThrow("_id"))
} else {
Log.w(TAG, "No active threads found for deletion benchmark")
return
}
}
Log.i(TAG, "Deleting thread $threadId")
SignalDatabase.threads.deleteConversation(threadId, syncThreadDelete = false)
Log.i(TAG, "Thread $threadId deleted")
}
private fun getOutgoingGroupMessageTimestamps(): List<Long> {
val groupId = GroupId.v2(Harness.groupMasterKey)
val groupRecipient = Recipient.externalGroupExact(groupId)

View File

@@ -1,7 +1,15 @@
package org.signal.benchmark
import android.os.Bundle
import android.widget.TextView
import androidx.activity.compose.setContent
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.signal.benchmark.setup.TestMessages
import org.signal.benchmark.setup.TestUsers
import org.thoughtcrime.securesms.BaseActivity
@@ -15,19 +23,29 @@ class BenchmarkSetupActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (intent.extras!!.getString("setup-type")) {
"cold-start" -> setupColdStart()
"conversation-open" -> setupConversationOpen()
"message-send" -> setupMessageSend()
"group-message-send" -> setupGroupMessageSend()
"group-delivery-receipt" -> setupGroupReceipt(includeMsl = true)
"group-read-receipt" -> setupGroupReceipt(enableReadReceipts = true)
var setupComplete by mutableStateOf(false)
setContent {
if (setupComplete) {
Text("done")
} else {
CircularProgressIndicator()
}
}
val textView: TextView = TextView(this).apply {
text = "done"
lifecycleScope.launch(Dispatchers.IO) {
when (intent.extras!!.getString("setup-type")) {
"cold-start" -> setupColdStart()
"conversation-open" -> setupConversationOpen()
"message-send" -> setupMessageSend()
"group-message-send" -> setupGroupMessageSend()
"group-delivery-receipt" -> setupGroupReceipt(includeMsl = true)
"group-read-receipt" -> setupGroupReceipt(enableReadReceipts = true)
"thread-delete" -> setupThreadDelete()
"thread-delete-group" -> setupThreadDeleteGroup()
}
setupComplete = true
}
setContentView(textView)
}
private fun setupColdStart() {
@@ -74,6 +92,65 @@ class BenchmarkSetupActivity : BaseActivity() {
TestUsers.setupGroup()
}
private fun setupThreadDelete() {
TestUsers.setupSelf()
val recipientIds = TestUsers.setupTestRecipients(2)
val recipient = Recipient.resolved(recipientIds[0])
val reactionAuthor = recipientIds[1]
val messagesToAdd = 20_000
val generator = TestMessages.TimestampGenerator(System.currentTimeMillis() - (messagesToAdd * 2000L) - 60_000L)
for (i in 0 until messagesToAdd) {
val timestamp = generator.nextTimestamp()
when {
i % 20 == 0 -> TestMessages.insertIncomingVoiceMessage(other = recipient, timestamp = timestamp)
i % 4 == 0 -> TestMessages.insertIncomingImageMessage(other = recipient, attachmentCount = 1, timestamp = timestamp)
else -> TestMessages.insertIncomingTextMessage(other = recipient, body = "Message $i", timestamp = timestamp)
}
}
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient = recipient)
TestDbUtils.insertReactionsForThread(threadId, reactionAuthor, moduloFilter = 5)
SignalDatabase.threads.update(threadId, true)
}
private fun setupThreadDeleteGroup() {
TestUsers.setupSelf()
val groupId = TestUsers.setupGroup()
val groupRecipient = Recipient.externalGroupExact(groupId)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
val selfId = Recipient.self().id
val memberRecipientIds = SignalDatabase.groups.getGroup(groupId).get().members.filter { it != selfId }
val messagesToAdd = 20_000
val generator = TestMessages.TimestampGenerator(System.currentTimeMillis() - (messagesToAdd * 2000L) - 60_000L)
for (i in 0 until messagesToAdd) {
val timestamp = generator.nextTimestamp()
when {
i % 4 == 0 -> TestMessages.insertOutgoingImageMessage(other = groupRecipient, attachmentCount = 1, timestamp = timestamp)
else -> {
val message = OutgoingMessage(
recipient = groupRecipient,
body = "Message $i",
timestamp = timestamp,
isSecure = true
)
val insert = SignalDatabase.messages.insertMessageOutbox(message, threadId, false, null)
SignalDatabase.messages.markAsSent(insert.messageId, true)
}
}
}
TestDbUtils.insertGroupReceiptsForThread(threadId, memberRecipientIds)
TestDbUtils.insertReactionsForThread(threadId, memberRecipientIds[0], moduloFilter = 5)
TestDbUtils.insertMentionsForThread(threadId, memberRecipientIds[0], moduloFilter = 10)
SignalDatabase.threads.update(threadId, true)
}
private fun setupGroupReceipt(includeMsl: Boolean = false, enableReadReceipts: Boolean = false) {
TestUsers.setupSelf()
val groupId = TestUsers.setupGroup()

View File

@@ -14,6 +14,62 @@ object TestDbUtils {
val rowsUpdated = database.update(MessageTable.TABLE_NAME, contentValues, DatabaseTable.ID_WHERE, buildArgs(messageId))
}
/**
* Bulk-inserts a reaction on every Nth message (by _id modulo) in the given thread.
*/
fun insertReactionsForThread(threadId: Long, authorId: RecipientId, moduloFilter: Int) {
val db = SignalDatabase.messages.databaseHelper.signalWritableDatabase
db.execSQL(
"""
INSERT INTO reaction (message_id, author_id, emoji, date_sent, date_received)
SELECT ${MessageTable.ID}, ?, '👍', ${MessageTable.DATE_SENT}, ${MessageTable.DATE_RECEIVED}
FROM ${MessageTable.TABLE_NAME}
WHERE ${MessageTable.THREAD_ID} = ? AND ${MessageTable.ID} % ? = 0
""".trimIndent(),
arrayOf(authorId.toLong().toString(), threadId.toString(), moduloFilter.toString())
)
}
/**
* Bulk-inserts group receipt rows for every message in the given thread, one row per member.
*/
fun insertGroupReceiptsForThread(threadId: Long, memberRecipientIds: List<RecipientId>) {
val db = SignalDatabase.messages.databaseHelper.signalWritableDatabase
db.beginTransaction()
try {
for (recipientId in memberRecipientIds) {
db.execSQL(
"""
INSERT INTO group_receipts (mms_id, address, status, timestamp)
SELECT ${MessageTable.ID}, ?, 2, ${MessageTable.DATE_SENT}
FROM ${MessageTable.TABLE_NAME}
WHERE ${MessageTable.THREAD_ID} = ?
""".trimIndent(),
arrayOf(recipientId.toLong().toString(), threadId.toString())
)
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
/**
* Bulk-inserts a mention on every Nth message (by _id modulo) in the given thread.
*/
fun insertMentionsForThread(threadId: Long, mentionedRecipientId: RecipientId, moduloFilter: Int) {
val db = SignalDatabase.messages.databaseHelper.signalWritableDatabase
db.execSQL(
"""
INSERT INTO mention (thread_id, message_id, recipient_id, range_start, range_length)
SELECT ${MessageTable.THREAD_ID}, ${MessageTable.ID}, ?, 0, 5
FROM ${MessageTable.TABLE_NAME}
WHERE ${MessageTable.THREAD_ID} = ? AND ${MessageTable.ID} % ? = 0
""".trimIndent(),
arrayOf(mentionedRecipientId.toLong().toString(), threadId.toString(), moduloFilter.toString())
)
}
fun getOutgoingMessageTimestamps(threadId: Long, selfRecipientId: Long): List<Long> {
val timestamps = mutableListOf<Long>()
SignalDatabase.messages.databaseHelper.signalReadableDatabase.query(