cancellation

Stopping/Cancelling scopes are independent of each other

In short cancelling one co-routine scope does not cancel other co-routine scopes. Other co-routines can continue to run, if they are not cancelled.

Code

package com.glassthought.sandbox

import gt.sandbox.util.output.Out
import kotlinx.coroutines.*

interface Server {
  suspend fun start()
  suspend fun stop()
  suspend fun join()
}

val out = Out.standard()

class ServerImpl(
  private val serverName: String,
  private val scope: CoroutineScope // Injected scope for better control
) : Server {
  private var backgroundJob: Job? = null

  override suspend fun start() {
    out.info("Starting server")

    backgroundJob = scope.launch(CoroutineName("${serverName}-work-1")) {
      out.info("Running server work in thread: ${Thread.currentThread().name}")

      launch {
        out.info("${serverName}: spawned from background job - work-A")
        delay(3000)
        out.info("${serverName}-work-A completed")
      }

      delay(2000)
      out.info("${serverName}-work-1 completed")

    }
  }

  override suspend fun stop() {
    out.info("Stopping server: $serverName")

    scope.cancel()
  }

  override suspend fun join() {
    if (backgroundJob != null) {
      backgroundJob!!.join()
    }
  }
}

fun main() = runBlocking {
  val server1 = ServerImpl("server-1", CoroutineScope(Dispatchers.Default))
  val server2 = ServerImpl("server-2", CoroutineScope(Dispatchers.Default))

  server1.start()
  server2.start()

  
  delay(10)
  // Stopping server 1 should not stop server - 2
  server1.stop()

  server1.join()
  server2.join()
  out.info("Main completed")
}

Command to reproduce:

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

Recorded output of command:

[elapsed:   39ms][šŸ„‡/tname:main/tid:1][coroutine:unnamed] Starting server
[elapsed:   57ms][šŸ„‡/tname:main/tid:1][coroutine:unnamed] Starting server
[elapsed:   57ms][ā“¶/tname:DefaultDispatcher-worker-1/tid:20][coroutine:server-1-work-1] Running server work in thread: DefaultDispatcher-worker-1
[elapsed:   57ms][ā“·/tname:DefaultDispatcher-worker-2/tid:21][coroutine:server-2-work-1] Running server work in thread: DefaultDispatcher-worker-2
[elapsed:   59ms][ā“ø/tname:DefaultDispatcher-worker-3/tid:22][coroutine:server-1-work-1] server-1: spawned from background job - work-A
[elapsed:   59ms][ā“¹/tname:DefaultDispatcher-worker-4/tid:23][coroutine:server-2-work-1] server-2: spawned from background job - work-A
[elapsed:   71ms][šŸ„‡/tname:main/tid:1][coroutine:unnamed] Stopping server: server-1
[elapsed: 2065ms][ā“·/tname:DefaultDispatcher-worker-2/tid:21][coroutine:server-2-work-1] server-2-work-1 completed
[elapsed: 3063ms][ā“·/tname:DefaultDispatcher-worker-2/tid:21][coroutine:server-2-work-1] server-2-work-A completed
[elapsed: 3064ms][šŸ„‡/tname:main/tid:1][coroutine:unnamed] Main completed


Children
  1. Stopping/Cancelling scopes have independent cancellations