NordicSemiconductor / Kotlin-BLE-Library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CoroutineScope in ServerBleGatt not propagated to the factory method

mottljan opened this issue · comments

In 1.0.7 possibility to provide own CoroutineScope to both ClientBleGatt and ServerBleGatt was added, which is great! However, in ServerBleGatt it was only added as a parameter to an internal constructor, but it was not propagated to the ServerBleGatt.create factory method and thus the scope can't be changed and default is used every time.

Also I realized one thing which you should consider. Using this ApplicationScope tied to the whole app's process is IMHO not a good idea, because the coroutines launched in this scope are never canceled and I think they should be canceled when I don't want to use ServerBleGatt/ClientBleGatt anymore and I disconnect or stop the server. The problem is that without coroutines cancellation it will leak those objects. In my case it is not unusual to create and clean up several BLE clients and servers during one app process. By using ApplicationScope those old client and server instances will never be garbage collected because onEach lambdas of event collecting coroutines will hold implicit reference to the client/server instances and will run there "forever" (until process is terminated).

Thanks for reporting. This is a good point.

I'm thinking about the best approach here. Do you think that providing a possibility to cancel the job should be enough or do you recommend to always use a dedicated scope to create client/server?

I think it would be best to force developers using your library to always provide their own scope, so they can manage it themself just by cancelling the scope. If there was some API to get a job, I am afraid that developers could easily miss this, even if it was a return value of some method that you can easily ignore and forget about. Also, if you added more coroutines in the future, we would need to handle cancellation of more jobs, but using a scope solution, we don't have to do any changes and the cancellation will work uniformly by just cancelling the scope. On the other hand particular jobs can give you more control over partial cancellations, but I can't think of any use case where this would be needed right now. I think I would always want to cancel the whole scope and clean up everything at the end of the lifetime of my client/server instance. IMHO scope seems definitely as a better and more robust solution.

One more thing I noticed. I was memory profiling the app using Android Studio's memory profiler to verify the memory leaks and even though ClientBleGatt was being garbage collected correctly when I used my own scope and cancelled it, ClientBleGattCallback was never garbage collected and I keep getting more and more instances with more ClientBleGatt instances. I didn't investigate further why this was happening, but I think it would be cool to check the library for memory leaks when you have some time 🙂

Managing one scope looks better than handling many tasks. I also changed library to not use ApplicationScope. I was inspired by this PR and the external scope is wrapped inside CoroutineScope(Dispatchers.Default + SupervisorJob(scope.coroutineContext.job)). Thanks to that if someone passes viewModelScope then the library will not cancel everything on disconnection. Hope this do its job.

Thanks for checking memory profiler. I created new issue for this so we won't forget.