From c499c4baec7f179679525c22b84f027b51bb8f51 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 7 Jun 2023 12:08:13 -0400 Subject: [PATCH] Adds more main thread checks to make sure slow procedures are run outside the main thread. --- .../com/vitorpamplona/amethyst/model/Note.kt | 3 +++ .../amethyst/model/ThreadAssembler.kt | 3 +++ .../amethyst/service/Nip05Verifier.kt | 3 +++ .../service/lnurl/LightningAddressResolver.kt | 3 +++ .../amethyst/service/lnurl/LnInvoiceUtil.kt | 5 ++++- .../service/previews/BahaUrlPreview.kt | 2 ++ .../amethyst/service/relays/Relay.kt | 18 ++++++++++++++++++ .../amethyst/service/relays/RelayPool.kt | 3 +++ .../ui/actions/JoinUserOrChannelView.kt | 2 ++ .../amethyst/ui/components/BundledUpdate.kt | 3 +++ .../amethyst/ui/components/Robohash.kt | 2 ++ .../ui/dal/ChatroomListKnownFeedFilter.kt | 3 +-- .../amethyst/ui/note/UserReactionsRow.kt | 4 ++++ .../amethyst/ui/screen/CardFeedViewModel.kt | 2 ++ .../amethyst/ui/screen/FeedViewModel.kt | 2 ++ .../amethyst/ui/screen/LnZapFeedViewModel.kt | 2 ++ .../amethyst/ui/screen/UserFeedViewModel.kt | 2 ++ .../ui/screen/loggedIn/SearchScreen.kt | 3 +++ 18 files changed, 62 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 27645d425..6153c7ff0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource +import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.service.nip19.Nip19 @@ -179,6 +180,7 @@ open class Note(val idHex: String) { @Synchronized fun addZap(zapRequest: Note, zap: Note?) { + checkNotInMainThread() if (zapRequest !in zaps.keys) { zaps = zaps + Pair(zapRequest, zap) liveSet?.zaps?.invalidateData() @@ -190,6 +192,7 @@ open class Note(val idHex: String) { @Synchronized fun addZapPayment(zapPaymentRequest: Note, zapPayment: Note?) { + checkNotInMainThread() if (zapPaymentRequest !in zapPayments.keys) { zapPayments = zapPayments + Pair(zapPaymentRequest, zapPayment) liveSet?.zaps?.invalidateData() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt index 25498f702..8b6c2b4ef 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.model +import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.model.ATag import kotlin.time.ExperimentalTime import kotlin.time.measureTimedValue @@ -35,6 +36,8 @@ class ThreadAssembler { @OptIn(ExperimentalTime::class) fun findThreadFor(noteId: String): Set { + checkNotInMainThread() + val (result, elapsed) = measureTimedValue { val note = if (noteId.contains(":")) { val aTag = ATag.parse(noteId, null) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05Verifier.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05Verifier.kt index 8ebc3789f..60e265bd8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05Verifier.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip05Verifier.kt @@ -33,6 +33,8 @@ class Nip05Verifier() { } private suspend fun fetchNip05JsonSuspend(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { + checkNotInMainThread() + val url = assembleUrl(nip05) if (url == null) { @@ -70,6 +72,7 @@ class Nip05Verifier() { } fun verifyNip05(nip05: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { + checkNotInMainThread() val mapper = jacksonObjectMapper() fetchNip05Json( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt index 78ea52018..92a0105e2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service.lnurl import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.service.HttpClient +import com.vitorpamplona.amethyst.service.checkNotInMainThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -45,6 +46,8 @@ class LightningAddressResolver() { } private suspend fun fetchLightningAddressJsonSuspend(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) { + checkNotInMainThread() + val url = assembleUrl(lnaddress) if (url == null) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LnInvoiceUtil.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LnInvoiceUtil.kt index 9587c8322..b2cce3447 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LnInvoiceUtil.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LnInvoiceUtil.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.service.lnurl +import com.vitorpamplona.amethyst.service.checkNotInMainThread import java.math.BigDecimal import java.util.Locale import java.util.regex.Pattern @@ -98,7 +99,7 @@ object LnInvoiceUtil { * @return invoice amount in bitcoins, zero if the invoice has no amount * @throws RuntimeException if invoice format is incorrect */ - fun getAmount(invoice: String): BigDecimal { + private fun getAmount(invoice: String): BigDecimal { try { decodeUnlimitedLength(invoice) // checksum must match } catch (e: AddressFormatException) { @@ -121,6 +122,8 @@ object LnInvoiceUtil { } fun getAmountInSats(invoice: String): BigDecimal { + checkNotInMainThread() + return getAmount(invoice).multiply(BigDecimal(100000000)) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/previews/BahaUrlPreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/BahaUrlPreview.kt index be7d3ddfb..3e7b12643 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/previews/BahaUrlPreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/BahaUrlPreview.kt @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.previews import android.net.Uri +import com.vitorpamplona.amethyst.service.checkNotInMainThread import kotlinx.coroutines.* import java.util.* @@ -18,6 +19,7 @@ class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) { } private suspend fun fetch(timeOut: Int = 30000) { + checkNotInMainThread() lateinit var urlInfoItem: UrlInfoItem if (checkIsImageUrl()) { urlInfoItem = UrlInfoItem(url = url, image = url) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index 54a337708..9fc4624f5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -72,6 +72,8 @@ class Relay( fun requestAndWatch() { checkNotInMainThread() requestAndWatch { + checkNotInMainThread() + // Sends everything. Client.allSubscriptions().forEach { sendFilter(requestId = it) @@ -92,6 +94,8 @@ class Relay( val listener = object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { + checkNotInMainThread() + afterEOSE = false isReady = true ping = response.receivedResponseAtMillis - response.sentRequestAtMillis @@ -102,6 +106,8 @@ class Relay( } override fun onMessage(webSocket: WebSocket, text: String) { + checkNotInMainThread() + eventDownloadCounterInBytes += text.bytesUsedInMemory() try { @@ -156,6 +162,8 @@ class Relay( } override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { + checkNotInMainThread() + listeners.forEach { it.onRelayStateChange( this@Relay, @@ -166,6 +174,8 @@ class Relay( } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { + checkNotInMainThread() + socket = null isReady = false afterEOSE = false @@ -174,6 +184,8 @@ class Relay( } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { + checkNotInMainThread() + errorCounter++ socket?.close(1000, "Normal close") @@ -212,6 +224,8 @@ class Relay( } fun sendFilter(requestId: String) { + checkNotInMainThread() + if (read) { if (isConnected()) { if (isReady) { @@ -236,6 +250,8 @@ class Relay( } fun sendFilterOnlyIfDisconnected() { + checkNotInMainThread() + if (socket == null) { // waits 60 seconds to reconnect after disconnected. if (Date().time / 1000 > closingTime + 60) { @@ -246,6 +262,8 @@ class Relay( } fun send(signedEvent: EventInterface) { + checkNotInMainThread() + if (signedEvent is RelayAuthEvent) { val event = """["AUTH",${signedEvent.toJson()}]""" socket?.send(event) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt index 23f2c4a25..0d2140c18 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.relays import androidx.lifecycle.LiveData +import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.model.Event import com.vitorpamplona.amethyst.service.model.EventInterface import kotlinx.coroutines.CoroutineScope @@ -48,6 +49,8 @@ object RelayPool : Relay.Listener { } fun requestAndWatch() { + checkNotInMainThread() + relays.forEach { it.requestAndWatch() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index 5fa9ac7e1..11666d4dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -58,6 +58,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource +import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.note.ChannelName import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay @@ -158,6 +159,7 @@ private fun RenderSearch( LaunchedEffect(Unit) { launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { + checkNotInMainThread() if (searchBarViewModel.isSearchingFun()) { searchBarViewModel.invalidateData() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt index 8e0e1c98a..6f3140e3c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.components +import com.vitorpamplona.amethyst.service.checkNotInMainThread import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -58,6 +59,8 @@ class BundledInsert( private var queue = LinkedBlockingQueue() fun invalidateList(newObject: T, onUpdate: suspend (Set) -> Unit) { + checkNotInMainThread() + queue.put(newObject) if (onlyOneInBlock.getAndSet(true)) { return diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt index 648922a35..eae25438e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt @@ -12,6 +12,7 @@ import coil.fetch.SourceResult import coil.request.ImageRequest import coil.request.Options import coil.size.Size +import com.vitorpamplona.amethyst.service.checkNotInMainThread import okio.Buffer import java.security.MessageDigest @@ -67,6 +68,7 @@ class HashImageFetcher( ) : Fetcher { override suspend fun fetch(): SourceResult { + checkNotInMainThread() val source = try { Buffer().apply { write(svgString(data.toString()).toByteArray()) } } finally { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt index 365f0f6c5..ff3a112f0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt @@ -1,6 +1,5 @@ package com.vitorpamplona.amethyst.ui.dal -import android.util.Log import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -84,7 +83,7 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter + checkNotInMainThread() + invalidateInsertData(newNotes) } } @@ -310,6 +313,7 @@ class UserReactionsViewModel(val account: Account) : ViewModel() { } override fun onCleared() { + collectorJob?.cancel() super.onCleared() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index c154723a4..e05b11597 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -314,6 +314,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { init { collectorJob = viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes -> + checkNotInMainThread() + if (localFilter is AdditiveFeedFilter && _feedContent.value is CardFeedState.Loaded) { invalidateInsertData(newNotes) } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 418a669ce..09b2fb3f1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -246,6 +246,8 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel(), I init { collectorJob = viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes -> + checkNotInMainThread() + if (localFilter is AdditiveFeedFilter && (_feedContent.value is FeedState.Loaded || _feedContent.value is FeedState.Empty) ) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt index f6adfa8ef..89ddec940 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt @@ -85,6 +85,8 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter) : View init { collectorJob = viewModelScope.launch(Dispatchers.IO) { + checkNotInMainThread() + LocalCache.live.newEventBundles.collect { newNotes -> invalidateData() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index c70743dba..756f5169b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -105,6 +105,8 @@ open class UserFeedViewModel(val dataSource: FeedFilter) : ViewModel(), In init { collectorJob = viewModelScope.launch(Dispatchers.IO) { + checkNotInMainThread() + LocalCache.live.newEventBundles.collect { newNotes -> invalidateData() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 92c1134e3..8f49c9c28 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -58,6 +58,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrGlobalDataSource import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource +import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.note.AboutDisplay import com.vitorpamplona.amethyst.ui.note.ChannelName @@ -231,6 +232,8 @@ private fun SearchBar( LaunchedEffect(Unit) { launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { + checkNotInMainThread() + if (searchBarViewModel.isSearchingFun()) { searchBarViewModel.invalidateData() }