diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PendingPniSignatureMessageTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/PendingPniSignatureMessageTable.kt index 06877b573f..080dfc8f08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/PendingPniSignatureMessageTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/PendingPniSignatureMessageTable.kt @@ -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 = 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, 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 } } }