Change the contract of CoroutineContext.isActive to return 'true' for contexts with no job in it. (#3639)
Otherwise, the API is becoming an error prone for being called from jobless entrypoints (i.e. 'suspend fun main' or Ktor handlers), as 'if (ctx.isActive)' is a well-established pattern for busy-wait or synchronous job.
It is now aligned with CoroutineScope.isActive behaviour.
Fixes #3300
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
index 65646dc..5f40dfc 100644
--- a/kotlinx-coroutines-core/common/src/Job.kt
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -539,7 +539,7 @@
/**
* Returns `true` when the [Job] of the coroutine in this context is still active
- * (has not completed and was not cancelled yet).
+ * (has not completed and was not cancelled yet) or the context does not have a [Job] in it.
*
* Check this property in long-running computation loops to support cancellation
* when [CoroutineScope.isActive] is not available:
@@ -550,11 +550,11 @@
* }
* ```
*
- * The `coroutineContext.isActive` expression is a shortcut for `coroutineContext[Job]?.isActive == true`.
+ * The `coroutineContext.isActive` expression is a shortcut for `get(Job)?.isActive ?: true`.
* See [Job.isActive].
*/
public val CoroutineContext.isActive: Boolean
- get() = this[Job]?.isActive == true
+ get() = get(Job)?.isActive ?: true
/**
* Cancels [Job] of this context with an optional cancellation cause.
diff --git a/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt b/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt
index c46f41a..b678b03 100644
--- a/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt
+++ b/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt
@@ -277,4 +277,15 @@
private fun scopePlusContext(c1: CoroutineContext, c2: CoroutineContext) =
(ContextScope(c1) + c2).coroutineContext
+
+ @Test
+ fun testIsActiveWithoutJob() {
+ var invoked = false
+ suspend fun testIsActive() {
+ assertTrue(coroutineContext.isActive)
+ invoked = true
+ }
+ ::testIsActive.startCoroutine(Continuation(EmptyCoroutineContext){})
+ assertTrue(invoked)
+ }
}