JuulLabs / kable

Kotlin Asynchronous Bluetooth Low-Energy
https://juullabs.github.io/kable
Apache License 2.0
845 stars 83 forks source link

Crash if connection not established and CoroutineScope cancels #142

Closed solvek closed 3 years ago

solvek commented 3 years ago

I am first subscribing for notifications (using observe) then trying to connect to my peripheral (connect). Actually my device is not available so no connection is established and no services discovered of course.

Then I am exiting from this screen (and thus the couroutine scope is cancelling). At this moment I am getting crash:

java.lang.IllegalStateException: Services have not been discovered for Peripheral(bluetoothDevice=00:80:E1:26:79:3D)
        at com.juul.kable.AndroidPeripheral.getPlatformServices(Peripheral.kt:126)
        at com.juul.kable.AndroidPeripheral.stopObservation$core_debug(Peripheral.kt:289)
        at com.juul.kable.Observers$acquire$4.invokeSuspend(Observers.kt:81)
        at com.juul.kable.Observers$acquire$4.invoke(Unknown Source:13)
        at com.juul.kable.Observers$acquire$4.invoke(Unknown Source:6)
        at kotlinx.coroutines.flow.FlowKt__EmittersKt.invokeSafely$FlowKt__EmittersKt(Emitters.kt:216)
        at kotlinx.coroutines.flow.FlowKt__EmittersKt.access$invokeSafely(Emitters.kt:1)
        at kotlinx.coroutines.flow.FlowKt__EmittersKt$onCompletion$$inlined$unsafeFlow$1.collect(SafeCollector.common.kt:121)
        at kotlinx.coroutines.flow.FlowKt__EmittersKt$onCompletion$$inlined$unsafeFlow$1$1.invokeSuspend(Unknown Source:15)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.internal.ScopeCoroutine.afterResume(Scopes.kt:33)
        at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:102)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:233)
        at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
        at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:244)
        at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:161)
        at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:398)
        at kotlinx.coroutines.CancellableContinuationImpl.cancel(CancellableContinuationImpl.kt:184)
        at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core(CancellableContinuationImpl.kt:191)
        at kotlinx.coroutines.ChildContinuation.invoke(JobSupport.kt:1474)
        at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1499)
        at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:795)
        at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:755)
        at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:671)
        at kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:637)
        at kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1465)
        at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1499)
        at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:795)
        at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:755)
        at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:671)
        at kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:637)
        at kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1465)
        at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1499)
        at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:795)
        at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:755)
        at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:671)
        at kotlinx.coroutines.JobSupport.cancelInternal(JobSupport.kt:632)
        at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:617)
        at kotlinx.coroutines.Job$DefaultImpls.cancel$default(Job.kt:183)
        at androidx.lifecycle.BlockRunner$cancel$1.invokeSuspend(CoroutineLiveData.kt:191)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
        at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
        at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:398)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:432)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:421)

As I understand it tries to stop observation which actually was not started because no connection established... it is not unclear for me why it requires discovered services and crashes?

twyatt commented 3 years ago

Thanks for reporting! I'll take a look ~within the next couple days~ this weekend.

twyatt commented 3 years ago

@solvek can you give the following SNAPSHOT a try and see if it fixes the issue, please?

repositories {
    maven("https://oss.sonatype.org/content/repositories/snapshots")
}

dependencies {
    implementation("com.juul.kable:core:0.7.0-issue-142-1-SNAPSHOT")
}

Thank you!

solvek commented 3 years ago

Thank you Travis for quick fix. Tried this build and the crash now disappeared.

twyatt commented 3 years ago

Fixed in 0.7.1