app: TinyrssAccountTokenRetriever don't spam invalidateToken
As the default TinyTinyRss server as a throttling limitation on login, we
don't want to have multiple threads invalidating the token and forcing us to
make multiple login requests in a row.
To do that we make TinyrssAccountTokenRetriever threadsafe by making each
request run in a queue, and let clients wait for result.
--- a/app/src/main/java/com/geekorum/ttrss/accounts/TinyrssAccountTokenRetriever.kt Wed May 19 11:57:03 2021 -0400
+++ b/app/src/main/java/com/geekorum/ttrss/accounts/TinyrssAccountTokenRetriever.kt Wed May 19 00:24:16 2021 -0400
@@ -25,8 +25,12 @@
import com.geekorum.geekdroid.accounts.AccountTokenRetriever
import com.geekorum.ttrss.core.CoroutineDispatchersProvider
import com.geekorum.ttrss.webapi.TokenRetriever
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.actor
+import kotlin.time.Duration
+import kotlin.time.ExperimentalTime
+import kotlin.time.TimeMark
+import kotlin.time.TimeSource
/**
* An [AccountTokenRetriever] for Tinyrss.
@@ -36,17 +40,64 @@
private val dispatchers: CoroutineDispatchersProvider,
accountManager: AccountManager,
account: Account
-) : AccountTokenRetriever(
+) : TokenRetriever, AccountTokenRetriever(
accountManager,
AccountAuthenticator.TTRSS_AUTH_TOKEN_SESSION_ID,
account,
- true), TokenRetriever {
+ true
+) {
- override suspend fun getToken(): String = withContext(dispatchers.io) {
+ @OptIn(ObsoleteCoroutinesApi::class, DelicateCoroutinesApi::class, ExperimentalTime::class)
+ private val mailbox = GlobalScope.actor<Message>{
+ var lastMsg: Message? = null
+ var lastMsgTimeMark: TimeMark? = null
+ for (msg in channel) {
+ when {
+ msg is Message.GetToken -> {
+ val result = runCatching {
+ getTokenInternal()
+ }
+ msg.token.completeWith(result)
+ }
+ msg is Message.InvalidateToken && lastMsg == msg
+ -> {
+ val invalidate = (lastMsgTimeMark == null || lastMsgTimeMark.elapsedNow() >= Duration.seconds(6))
+ if (invalidate) {
+ invalidateTokenInternal()
+ lastMsgTimeMark = TimeSource.Monotonic.markNow()
+ }
+ }
+ }
+ lastMsg = msg
+ }
+ }
+
+ override suspend fun getToken(): String {
+ val response = CompletableDeferred<String>()
+ val getMsg = Message.GetToken(response)
+ mailbox.send(getMsg)
+ return response.await()
+ }
+
+ override suspend fun invalidateToken() {
+ mailbox.send(Message.InvalidateToken)
+ }
+
+
+ private suspend fun getTokenInternal(): String = withContext(dispatchers.io) {
try {
super.getToken()
} catch (e: com.geekorum.geekdroid.network.TokenRetriever.RetrieverException) {
throw TokenRetriever.RetrieverException("unable to retrieve token from account", e)
}
}
+
+ private suspend fun invalidateTokenInternal() {
+ super.invalidateToken()
+ }
+
+ private sealed class Message {
+ class GetToken(val token: CompletableDeferred<String>) : Message()
+ object InvalidateToken: Message()
+ }
}