diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveDatabaseExecutor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveDatabaseExecutor.kt index 6ee124073e..eb5560db42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveDatabaseExecutor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveDatabaseExecutor.kt @@ -5,8 +5,7 @@ package org.thoughtcrime.securesms.backup.v2 -import org.signal.core.util.ThreadUtil -import org.signal.core.util.concurrent.SignalExecutors +import androidx.annotation.VisibleForTesting import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.util.ThrottledDebouncer import java.util.concurrent.ExecutionException @@ -19,7 +18,10 @@ import kotlin.time.Duration.Companion.seconds */ object ArchiveDatabaseExecutor { - val executor = Executors.newSingleThreadExecutor(SignalExecutors.NumberedThreadFactory("archive-db", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD)) + @VisibleForTesting + const val THREAD_NAME = "archive-db" + + private val executor = Executors.newSingleThreadExecutor { Thread(it, THREAD_NAME) } /** * By default, downloading/uploading an attachment wants to notify a bunch of database observation listeners. This slams the observer so hard that other @@ -39,6 +41,10 @@ object ArchiveDatabaseExecutor { } fun runBlocking(block: () -> T): T { + if (Thread.currentThread().name.equals(THREAD_NAME)) { + return block() + } + return try { executor.submit(block).get() } catch (e: ExecutionException) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt index 53ed5b54a7..5aeb97ba21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/UploadAttachmentToArchiveJob.kt @@ -360,10 +360,8 @@ class UploadAttachmentToArchiveJob private constructor( } private fun setArchiveTransferStateWithDelayedNotification(attachmentId: AttachmentId, transferState: AttachmentTable.ArchiveTransferState) { - ArchiveDatabaseExecutor.runBlocking { - SignalDatabase.attachments.setArchiveTransferState(attachmentId, transferState, notify = false) - ArchiveDatabaseExecutor.throttledNotifyAttachmentObservers() - } + SignalDatabase.attachments.setArchiveTransferState(attachmentId, transferState, notify = false) + ArchiveDatabaseExecutor.throttledNotifyAttachmentObservers() } class Factory : Job.Factory { diff --git a/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ArchiveDatabaseExecutorTest.kt b/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ArchiveDatabaseExecutorTest.kt new file mode 100644 index 0000000000..0ff0ca18ab --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ArchiveDatabaseExecutorTest.kt @@ -0,0 +1,66 @@ +package org.thoughtcrime.securesms.backup.v2 + +import android.app.Application +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, application = Application::class) +class ArchiveDatabaseExecutorTest { + + @Test + fun `runBlocking returns the result of the block`() { + val result = ArchiveDatabaseExecutor.runBlocking { + 42 + } + + assertThat(result).isEqualTo(42) + } + + @Test + fun `nested runBlocking calls do not deadlock`() { + val completed = AtomicBoolean(false) + val result = AtomicReference() + val latch = CountDownLatch(1) + + Thread { + try { + val r = ArchiveDatabaseExecutor.runBlocking { + ArchiveDatabaseExecutor.runBlocking { + ArchiveDatabaseExecutor.runBlocking { + "nested-result" + } + } + } + result.set(r) + completed.set(true) + } finally { + latch.countDown() + } + }.start() + + val finishedInTime = latch.await(5, TimeUnit.SECONDS) + + assertThat(finishedInTime).isTrue() + assertThat(completed.get()).isTrue() + assertThat(result.get()).isEqualTo("nested-result") + } + + @Test + fun `runBlocking executes on archive-db thread`() { + val threadName = ArchiveDatabaseExecutor.runBlocking { + Thread.currentThread().name + } + + assertThat(threadName).isEqualTo(ArchiveDatabaseExecutor.THREAD_NAME) + } +}