Leaking Service held by Coroutine
Asked Answered
N

1

3

I'm trying to dive into coroutines world, trying to make some network work in Service (in fact LifecycleService). after starting it I'm calling below method

private fun runConnectionCoroutine() {
    job = lifecycleScope.launch(Dispatchers.IO) {
        try {
            doSomeNetworking()
            if (!isActive) return@launch
            publishResult()
        }catch (e: CancellationException){
        }
    }
}

doSomeNetworking() may take too much time, so user should be able to dimiss this task and Service. I'm calling then job?.cancel() and then stopSelf(). As expected coroutine doesn't stop immediatelly (thats why if (!isActive) return@launch line is there), but Service does and leakcanary detects: CoroutineScheduler$Worker keeps reference to this Service

so the question is: how to properly stop Service during networking coroutine?

Noland answered 22/6, 2021 at 9:12 Comment(5)
This is actually a very interesting question. I guess it is not always possible to not leak anything at least for a fraction of a second. I don't know leakcanary, so I'm not sure if it allows some time margin or not. Still, your code isn't really very cancellation-friendly. As you noticed, it can cancel only after the long running operation finishes, so this isActive check does not make too much of a difference. What we need to do is to check for isActive during the long running task. And do it regularly, e.g. each 100ms.Sirrah
Specific solution depends on your implementation of doSomeNetworking(). If you are performing blocking I/O reads then usually there are some alternatives to do it in a non-blocking way or at least to specify a timeout. This way you can read data and at the same time verify isActive status frequently. You can also switch I/O API/library to one that is fully non-blocking, e.g.: okio or NIO, etc.Sirrah
doSomeNetworking() in my case is using GRPC and I've configured some timeouts, but still there is a case, when networking starts at some point and after 10 ms Service gets killed for some not-networking-related reason (e.g. user desire). I'm canceling job in onDestroy, but this coroutine still works and hold reference to killed Service, so leak... anyway, dzięki za komentarzNoland
Does suspendCancellableCoroutine help you?Hargeisa
@RickyMo can you post some example usage of this method?Noland
S
0

I think there are 2 cases that you need to pay attention:

  1. As @broot explained, this line is redundant, remove it.

if (!isActive) return@launch

  1. Check if the coroutine is not cancelled (isActive = true) for every operation in doSomeNetworking
  2. Cancel the coroutine in service's onDestroy method to ensure the coroutine is shut down when any condition happens, not just the users choose to shut down because the OS also can whenever it finds the resource is not enough.
Surgy answered 8/4, 2024 at 6:27 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.