Salah Hammouda
Salah Hammouda

Reputation: 303

Kotlin Coroutine SupervisorJob canceling behaviour

code:

viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
                Log.e("TAG", "task 1")
            }) {
                try {
                    Log.e("TAG", "task 1 start")
                    delay(3000)
                    Log.e("TAG", "task 1 finished")
                } catch (ex: Exception) {
                    Log.e("TAG", "task 1 cancelled " + ex)
                }
            }

    launch = viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
        Log.e("TAG", "handler 1" + myJob?.isCancelled)
    }) {

        myJob = SupervisorJob()
        launch(myJob!! + CoroutineExceptionHandler { _, _ ->
            Log.e("TAG", "handler 2 " + myJob?.isCancelled)
        }) {

            delay(300)
            launch {
                try {
                    Log.e("TAG", "task 2 start")
                    delay(5000)
                    Log.e("TAG", "task 2 finished")
                } catch (ex: Exception) {
                    Log.e("TAG", "task 2 ex " + ex)
                }
            }

            launch {
                delay(2000)
                throw Exception()
            }

        }

        launch {
            try {
                Log.e("TAG", "task 3 start")
                delay(3000)
                Log.e("TAG", "task 3 finished")
            } catch (ex: Exception) {
                Log.e("TAG", "task 3 ex " + ex)
            }
        }
    }

output:

2020-01-06 09:47:56.462 7159-7159/? E/TAG: task 1 start
2020-01-06 09:47:56.496 7159-7159/? E/TAG: task 3 start
2020-01-06 09:47:56.798 7159-7159/com.mvvm.template.debug E/TAG: task 2 start
2020-01-06 09:47:58.822 7159-7159/com.mvvm.template.debug E/TAG: task 2 ex kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=StandaloneCoroutine{Cancelling}@81a8e39
2020-01-06 09:47:58.827 7159-7159/com.mvvm.template.debug E/TAG: handler 2 false
2020-01-06 09:47:59.464 7159-7159/com.mvvm.template.debug E/TAG: task 1 finished
2020-01-06 09:47:59.499 7159-7159/com.mvvm.template.debug E/TAG: task 3 finished

my issue:

I’m having an issue to understand why task 2 is canceled when it’s a child of SupervisorJob and the exception happened on an other child.

The documentation state:

A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children, so a supervisor can implement a custom policy for handling failures of its children. Am i missing something or what ? any help would be appreciated.

Upvotes: 3

Views: 1821

Answers (2)

Zain
Zain

Reputation: 40878

I’m having an issue to understand why task 2 is canceled when it’s a child of SupervisorJob and the exception happened on an other child.

Here this is not a direct answer to the question as it's already answered well by the accepted answer as the parent is not the SupervisorJob

But in order to achieve the intended behavior of making task2 independent of the sibling jobs:

You've to use SupervisorJob by either:

  1. Making the SupervisorJob the parent of each task that you want to be independent of one another:

In your example:

val myJob = SupervisorJob()
    val launch = viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
        Log.e("TAG", "handler 1" + myJob.isCancelled)
    }) {

        launch(CoroutineExceptionHandler { _, _ ->
            Log.e("TAG", "handler 2 " + myJob.isCancelled)
        }) {

            delay(300)
            launch(myJob) { // <<<<<<<<< myJob is the parent
                try {
                    Log.e("TAG", "task 2 start")
                    delay(5000)
                    Log.e("TAG", "task 2 finished")
                } catch (ex: Exception) {
                    Log.e("TAG", "task 2 ex $ex")
                }
            }

            launch(myJob) { // <<<<<<<<< myJob is the parent
                delay(2000)
                throw Exception()
            }

        }

        //......
    }
  1. Add the child job siblings within a supervisorScope suspend function
val launch = viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
    Log.e("TAG", "handler 1")
}) {
    launch(CoroutineExceptionHandler { _, _ ->
        Log.e("TAG", "handler 2 ")
    }) {

        delay(300)

        supervisorScope { /// <<<< supervisor scope
            launch {
                try {
                    Log.e("TAG", "task 2 start")
                    delay(5000)
                    Log.e("TAG", "task 2 finished")
                } catch (ex: Exception) {
                    Log.e("TAG", "task 2 ex $ex")
                }
            }

            launch {
                delay(2000)
                throw Exception()
            }
        }

    }

    //....
}

Now task 2 will complete without raising JobCancellationException

Upvotes: 2

Marko Topolnik
Marko Topolnik

Reputation: 200196

Your answer is right there in the log:

task 2 ex kotlinx.coroutines.JobCancellationException: Parent job is Cancelling;
job=StandaloneCoroutine{Cancelling}@81a8e39

Look at the parent job: it's a StandaloneCoroutine and not your SupervisorJob.

When you write

launch(myJob!!, handler) { ... }

myJob becomes the parent of the launched coroutine and the coroutine itself is always associated with a job the launch function creates for it, of the type StandaloneCoroutine. Inside this coroutine you launch more coroutines without explicitly specifying a parent, which means their parent is the coroutine's job. It is not a supervisor job and gets canceled.

Upvotes: 1

Related Questions