Stanislav Kral
10/24/2023, 6:21 PMprivate var sdkCoroutineContext: CoroutineContext? = null
/**
* Manages the [sdkCoroutineContext]
*/
fun connect() {
sdk
.bindSdkService(context)
.doOnNext { event ->
log.d("bindSdkService event: $event")
if (event is SdkConnectedEvent) {
// create SDK coroutine context
sdkCoroutineContext = newSingleThreadContext("sdk ${event.hashCode()}") + Job()
} else if (event is SdkDisconnectedEvent) {
// cancel SDK coroutine context
sdkCoroutineContext?.cancel(CancellationException("SDK disconnected"))
sdkCoroutineContext = null
}
}
.subscribe()
}
fun accessSdk(action: Sdk.() -> T): T {
return try {
runBlocking {
withTimeout(timeout) {
sdkCoroutineContext?.let { context ->
val deferred = async(context) {
log.d("action starting")
action.invoke(sdk).also {
log.d("action finished")
}
}
deferred.await()
}
}
} ?: throw SdkNotConnected.also {
log.e("SDK is not connected")
}
} catch (e: CancellationException) {
log.e(e, "Action hasn't been processed in time")
throw SdkTimeoutException
} catch (e: InterruptedException) {
log.e(e, "runBlocking was interrupted when waiting for SDK action")
throw SdkTimeoutException
}
}
This way if the library code hangs I receive CancellationException
.
Even though the SdkDisconnectedEvent
event is emitted only rarely, I understand that even though I call sdkCoroutineContext.cancel()
the thread won't stop since it is non-cooperative (it may be stuck somewhere inside the library).
Are there any obvious flaws with this approach? Such as expensive synchronization between threads or too many resources leaking due to the library's code being non-cooperative.
Thank you very much.ephemient
10/24/2023, 6:48 PMephemient
10/24/2023, 6:49 PMephemient
10/24/2023, 6:50 PMStanislav Kral
10/24/2023, 7:59 PMrunInterruptible
as it works exactly as I need - you just saved me the whole thing with another coroutine context managed by a separate thread 😄Stanislav Kral
10/24/2023, 8:16 PMrunBlocking
:/Stanislav Kral
10/24/2023, 8:18 PMStanislav Kral
10/24/2023, 9:02 PMprivate var sdkCoroutineContext: CoroutineContext? = null
fun <T> useSdk(action: Sdk.() -> T): T =
try {
runBlocking {
sdkCoroutineContext?.let { coroutineContext ->
withContext(coroutineContext) {
runInterruptible {
action.invoke(sdk)
}
}
} ?: throw SdkNotConnected.also {
log.e("SDK is not connected")
}
}
} catch (e: CancellationException) {
log.e(e, "SDK action was cancelled")
throw SdkNotConnected
} catch (e: InterruptedException) {
log.e(e, "runBlocking was interrupted when waiting for SDK action")
throw SdkNotConnected
}
When the SDK is disconnected and my code gets notified, the sdkCoroutineContext
is cancelled, and useSdk
returns immediately.uli
11/01/2023, 3:21 PM