Improve receipt perf by caching the pending PNI signature table.

This commit is contained in:
Greyson Parrelli 2023-11-10 12:10:14 -08:00
parent ebef4b079c
commit b7ec913cb9

View file

@ -8,7 +8,10 @@ import org.signal.core.util.logging.Log
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.LRUCache
import org.whispersystems.signalservice.api.messages.SendMessageResult
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock
/**
* Contains records of messages that have been sent with PniSignatures on them.
@ -41,45 +44,71 @@ class PendingPniSignatureMessageTable(context: Context, databaseHelper: SignalDa
)
}
/**
* Caches whether or not there are any pending PNI signature messages for a given recipient.
* - If there are, there will be an entry with 'true' for that recipient.
* - If there aren't, there will be an entry with 'false'.
* - If the entry is null, we do not know, and you should check the database.
*
* This cache exists because this table is hit very frequently via delivery receipt handling.
*/
private val pendingPniSignaturesCache: MutableMap<RecipientId, Boolean?> = LRUCache(1000)
private val pendingPniSignaturesCacheLock = ReentrantReadWriteLock()
fun insertIfNecessary(recipientId: RecipientId, sentTimestamp: Long, result: SendMessageResult) {
if (!result.isSuccess) {
if (!result.isSuccess || result.success.devices.isEmpty()) {
return
}
writableDatabase.withinTransaction { db ->
for (deviceId in result.success.devices) {
val values = contentValuesOf(
RECIPIENT_ID to recipientId.serialize(),
SENT_TIMESTAMP to sentTimestamp,
DEVICE_ID to deviceId
)
pendingPniSignaturesCacheLock.writeLock().withLock {
writableDatabase.withinTransaction { db ->
for (deviceId in result.success.devices) {
val values = contentValuesOf(
RECIPIENT_ID to recipientId.serialize(),
SENT_TIMESTAMP to sentTimestamp,
DEVICE_ID to deviceId
)
db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE)
db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE)
}
}
pendingPniSignaturesCache[recipientId] = true
}
}
fun acknowledgeReceipts(recipientId: RecipientId, sentTimestamps: Collection<Long>, deviceId: Int) {
writableDatabase.withinTransaction { db ->
val count = db
.delete(TABLE_NAME)
.where("$RECIPIENT_ID = ? AND $SENT_TIMESTAMP IN (?) AND $DEVICE_ID = ?", recipientId, sentTimestamps.joinToString(separator = ","), deviceId)
.run()
if (count <= 0) {
return@withinTransaction
pendingPniSignaturesCacheLock.readLock().withLock {
if (pendingPniSignaturesCache[recipientId] == false) {
return
}
}
val stillPending: Boolean = db.exists(TABLE_NAME).where("$RECIPIENT_ID = ? AND $SENT_TIMESTAMP = ?", recipientId, sentTimestamps).run()
if (!stillPending) {
Log.i(TAG, "All devices for ($recipientId, $sentTimestamps) have acked the PNI signature message. Clearing flag and removing any other pending receipts.")
SignalDatabase.recipients.clearNeedsPniSignature(recipientId)
db
pendingPniSignaturesCacheLock.writeLock().withLock {
writableDatabase.withinTransaction { db ->
val count = db
.delete(TABLE_NAME)
.where("$RECIPIENT_ID = ?", recipientId)
.where("$RECIPIENT_ID = ? AND $SENT_TIMESTAMP IN (?) AND $DEVICE_ID = ?", recipientId, sentTimestamps.joinToString(separator = ","), deviceId)
.run()
if (count <= 0) {
pendingPniSignaturesCache[recipientId] = false
return@withinTransaction
}
val stillPending: Boolean = db.exists(TABLE_NAME).where("$RECIPIENT_ID = ? AND $SENT_TIMESTAMP = ?", recipientId, sentTimestamps).run()
if (!stillPending) {
Log.i(TAG, "All devices for ($recipientId, $sentTimestamps) have acked the PNI signature message. Clearing flag and removing any other pending receipts.")
SignalDatabase.recipients.clearNeedsPniSignature(recipientId)
db
.delete(TABLE_NAME)
.where("$RECIPIENT_ID = ?", recipientId)
.run()
}
pendingPniSignaturesCache[recipientId] = stillPending
}
}
}