coroutineScope: cancellation-exception-only-stops-the-throwing-child

CancellationException only stops the child co-routine that threw it allowing others to finish.

GT-Sandbox-Snapshot

Code

package com.glassthought.sandbox

import com.glassthought.sandbox.util.out.impl.out
import gt.sandbox.util.output.Out
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.seconds

private suspend fun mainImpl(out: Out) {
  coroutineScope {
    foo()
  }
}

private suspend fun foo() {
  val result = out.actionWithMsg("functionThatStartsCoRoutineScope()", { functionThatStartsCoRoutineScope() })

  out.info("Result from functionThatStartsCoRoutineScope() is: $result")
}

private suspend fun functionThatStartsCoRoutineScope(): String {
  return coroutineScope {
    out.info("This coroutineScope suspends the calling function.")

    val resultDeferred = async(CoroutineName("async-wait-3s")) {
      out.delayNamed(3.seconds)

      "result-from-async-wait-3s"
    }

    async(CoroutineName("async-throws-cancellation-after-2s")) {
      out.delayNamed(2.seconds, "delay before throwing CancellationException")

      out.info("OK I am throwing CancellationException now!")
      throw CancellationException("I am throwing cancellation")
    }

    launch(CoroutineName("launch-waits-1s")) {
      out.delayNamed(1.seconds)
    }

    out.info("Just launched co-routines")
    resultDeferred.await()
  }

}

fun main(): Unit = runBlocking {
  out.info("START - ON MAIN")

  try {
    mainImpl(out)
  } catch (e: Exception) {
    out.error("back at MAIN got an exception! of type=[${e::class.simpleName}] with msg=[${e.message}] cause=[${e.cause}]. Exiting with error code 1")

    exitProcess(1)
  }

  out.info("DONE - WITHOUT errors on MAIN")
}

Command to reproduce:

gt.sandbox.checkout.commit aa4b136e407661c441cc \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew run --quiet"

Recorded output of command:

Picked up JAVA_TOOL_OPTIONS: -Dkotlinx.coroutines.debug
Picked up JAVA_TOOL_OPTIONS: -Dkotlinx.coroutines.debug
[INFO][elapsed:   14ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed:   30ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[functionThatStartsCoRoutineScope()] 
[INFO][elapsed:   30ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1]    This coroutineScope suspends the calling function.
[INFO][elapsed:   32ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1]    Just launched co-routines
[INFO][elapsed:   36ms][🥇][⓶][coroutname:@async-wait-3s#2][tname:main/tid:1] Delaying for 3s what_for=[]
[INFO][elapsed:   37ms][🥇][⓷][coroutname:@async-throws-cancellation-after-2s#3][tname:main/tid:1] Delaying for 2s what_for=[delay before throwing CancellationException]
[INFO][elapsed:   37ms][🥇][⓸][coroutname:@launch-waits-1s#4][tname:main/tid:1] Delaying for 1s what_for=[]
[INFO][elapsed: 1038ms][🥇][⓸][coroutname:@launch-waits-1s#4][tname:main/tid:1] Done delaying for 1s what_for=[]
[INFO][elapsed: 2037ms][🥇][⓷][coroutname:@async-throws-cancellation-after-2s#3][tname:main/tid:1] Done delaying for 2s what_for=[delay before throwing CancellationException]
[INFO][elapsed: 2038ms][🥇][⓷][coroutname:@async-throws-cancellation-after-2s#3][tname:main/tid:1] OK I am throwing CancellationException now!
[INFO][elapsed: 3036ms][🥇][⓶][coroutname:@async-wait-3s#2][tname:main/tid:1] Done delaying for 3s what_for=[]
[INFO][elapsed: 3038ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] [<] Finished action=[functionThatStartsCoRoutineScope()].
[INFO][elapsed: 3038ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] Result from functionThatStartsCoRoutineScope() is: result-from-async-wait-3s
[INFO][elapsed: 3038ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] DONE - WITHOUT errors on MAIN

Backlinks