com.pubnub.api.endpoints.remoteaction.RetryingRemoteAction.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pubnub-kotlin Show documentation
Show all versions of pubnub-kotlin Show documentation
PubNub is a cross-platform client-to-client (1:1 and 1:many) push service in the cloud, capable of
broadcasting real-time messages to millions of web and mobile clients simultaneously, in less than a quarter
second!
package com.pubnub.api.endpoints.remoteaction
import com.pubnub.api.PubNubError
import com.pubnub.api.PubNubException
import com.pubnub.api.enums.PNOperationType
import com.pubnub.api.enums.PNStatusCategory
import com.pubnub.api.models.consumer.PNStatus
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ExecutorService
import java.util.concurrent.atomic.AtomicReference
internal class RetryingRemoteAction(
private val remoteAction: ExtendedRemoteAction,
private val maxNumberOfAutomaticRetries: Int,
private val operationType: PNOperationType,
private val executorService: ExecutorService
) : ExtendedRemoteAction {
private lateinit var cachedCallback: (result: T?, status: PNStatus) -> Unit
@Throws(PubNubException::class)
override fun sync(): T? {
validate()
var thrownException: PubNubException? = null
for (i in 0 until maxNumberOfAutomaticRetries) {
thrownException = try {
return remoteAction.sync()
} catch (ex: PubNubException) {
ex
}
}
throw thrownException!!
}
override fun async(callback: (result: T?, status: PNStatus) -> Unit) {
cachedCallback = callback
executorService.execute(
Runnable {
try {
validate()
} catch (ex: PubNubException) {
callback(
null,
PNStatus(
operation = operationType,
error = true,
exception = ex,
category = PNStatusCategory.PNBadRequestCategory
).apply {
executedEndpoint = this@RetryingRemoteAction
}
)
return@Runnable
}
var lastResultAndStatus: ResultAndStatus? = null
for (i in 0 until maxNumberOfAutomaticRetries) {
lastResultAndStatus = syncAsync()
if (!lastResultAndStatus.status.error) {
callback(lastResultAndStatus.result, lastResultAndStatus.status)
return@Runnable
}
}
callback(lastResultAndStatus!!.result, lastResultAndStatus.status)
}
)
}
override fun retry() {
async(cachedCallback)
}
override fun silentCancel() {
remoteAction.silentCancel()
}
override fun operationType(): PNOperationType {
return remoteAction.operationType()
}
private data class ResultAndStatus(
val result: T? = null,
val status: PNStatus
)
private fun syncAsync(): ResultAndStatus {
val latch = CountDownLatch(1)
val atomicResultAndStatus = AtomicReference>()
remoteAction.async { result: T?, status: PNStatus ->
atomicResultAndStatus.set(
ResultAndStatus(
result,
status.copy().apply { executedEndpoint = this@RetryingRemoteAction }
)
)
latch.countDown()
}
return try {
latch.await()
atomicResultAndStatus.get()
} catch (e: InterruptedException) {
remoteAction.silentCancel()
ResultAndStatus(
null,
PNStatus(
category = PNStatusCategory.PNUnknownCategory,
operation = operationType,
error = true,
exception = PubNubException(errorMessage = e.message)
).apply { executedEndpoint = this@RetryingRemoteAction }
)
}
}
@Throws(PubNubException::class)
private fun validate() {
if (maxNumberOfAutomaticRetries < 1) {
throw PubNubException(PubNubError.INVALID_ARGUMENTS)
}
}
companion object {
fun autoRetry(
remoteAction: ExtendedRemoteAction,
maxNumberOfAutomaticRetries: Int,
operationType: PNOperationType,
executorService: ExecutorService
): RetryingRemoteAction {
return RetryingRemoteAction(remoteAction, maxNumberOfAutomaticRetries, operationType, executorService)
}
}
}