xref: /aosp_15_r20/external/kotlinx.coroutines/kotlinx-coroutines-core/common/src/CoroutineScope.kt (revision 7a7160fed73afa6648ef8aa100d4a336fe921d9a)

<lambda>null1 @file:OptIn(ExperimentalContracts::class)
2 
3 package kotlinx.coroutines
4 
5 import kotlinx.coroutines.internal.*
6 import kotlinx.coroutines.intrinsics.*
7 import kotlin.contracts.*
8 import kotlin.coroutines.*
9 import kotlin.coroutines.intrinsics.*
10 
11 /**
12  * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc.)
13  * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
14  * to automatically propagate all its elements and cancellation.
15  *
16  * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions,
17  * taking care to cancel these coroutine scopes when they are no longer needed (see section on custom usage below for
18  * explanation and example).
19  *
20  * Additional context elements can be appended to the scope using the [plus][CoroutineScope.plus] operator.
21  *
22  * ### Convention for structured concurrency
23  *
24  * Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead.
25  * By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a
26  * [job][Job] to enforce the discipline of **structured concurrency** with propagation of cancellation.
27  *
28  * Every coroutine builder (like [launch], [async], and others)
29  * and every scoping function (like [coroutineScope] and [withContext]) provides _its own_ scope
30  * with its own [Job] instance into the inner block of code it runs.
31  * By convention, they all wait for all the coroutines inside their block to complete before completing themselves,
32  * thus enforcing the structured concurrency. See [Job] documentation for more details.
33  *
34  * ### Android usage
35  *
36  * Android has first-party support for coroutine scope in all entities with the lifecycle.
37  * See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
38  *
39  * ### Custom usage
40  *
41  * `CoroutineScope` should be declared as a property on entities with a well-defined lifecycle that are
42  * responsible for launching child coroutines. The corresponding instance of `CoroutineScope` shall be created
43  * with either `CoroutineScope()` or `MainScope()`:
44  *
45  * - `CoroutineScope()` uses the [context][CoroutineContext] provided to it as a parameter for its coroutines
46  *   and adds a [Job] if one is not provided as part of the context.
47  * - `MainScope()` uses [Dispatchers.Main] for its coroutines and has a [SupervisorJob].
48  *
49  * **The key part of custom usage of `CoroutineScope` is cancelling it at the end of the lifecycle.**
50  * The [CoroutineScope.cancel] extension function shall be used when the entity that was launching coroutines
51  * is no longer needed. It cancels all the coroutines that might still be running on behalf of it.
52  *
53  * For example:
54  *
55  * ```
56  * class MyUIClass {
57  *     val scope = MainScope() // the scope of MyUIClass, uses Dispatchers.Main
58  *
59  *     fun destroy() { // destroys an instance of MyUIClass
60  *         scope.cancel() // cancels all coroutines launched in this scope
61  *         // ... do the rest of cleanup here ...
62  *     }
63  *
64  *     /*
65  *      * Note: if this instance is destroyed or any of the launched coroutines
66  *      * in this method throws an exception, then all nested coroutines are cancelled.
67  *      */
68  *     fun showSomeData() = scope.launch { // launched in the main thread
69  *        // ... here we can use suspending functions or coroutine builders with other dispatchers
70  *        draw(data) // draw in the main thread
71  *     }
72  * }
73  * ```
74  */
75 public interface CoroutineScope {
76     /**
77      * The context of this scope.
78      * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
79      * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
80      *
81      * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
82      */
83     public val coroutineContext: CoroutineContext
84 }
85 
86 /**
87  * Adds the specified coroutine context to this scope, overriding existing elements in the current
88  * scope's context with the corresponding keys.
89  *
90  * This is a shorthand for `CoroutineScope(thisScope.coroutineContext + context)`.
91  */
plusnull92 public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope =
93     ContextScope(coroutineContext + context)
94 
95 /**
96  * Creates the main [CoroutineScope] for UI components.
97  *
98  * Example of use:
99  * ```
100  * class MyAndroidActivity {
101  *     private val scope = MainScope()
102  *
103  *     override fun onDestroy() {
104  *         super.onDestroy()
105  *         scope.cancel()
106  *     }
107  * }
108  * ```
109  *
110  * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
111  * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
112  * `val scope = MainScope() + CoroutineName("MyActivity")`.
113  */
114 @Suppress("FunctionName")
115 public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
116 
117 /**
118  * Returns `true` when the current [Job] is still active (has not completed and was not cancelled yet).
119  *
120  * Check this property in long-running computation loops to support cancellation:
121  * ```
122  * while (isActive) {
123  *     // do some computation
124  * }
125  * ```
126  *
127  * This property is a shortcut for `coroutineContext.isActive` in the scope when
128  * [CoroutineScope] is available.
129  * See [coroutineContext][kotlin.coroutines.coroutineContext],
130  * [isActive][kotlinx.coroutines.isActive] and [Job.isActive].
131  */
132 @Suppress("EXTENSION_SHADOWED_BY_MEMBER")
133 public val CoroutineScope.isActive: Boolean
134     get() = coroutineContext[Job]?.isActive ?: true
135 
136 /**
137  * A global [CoroutineScope] not bound to any job.
138  * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime
139  * and are not cancelled prematurely.
140  *
141  * Active coroutines launched in `GlobalScope` do not keep the process alive. They are like daemon threads.
142  *
143  * This is a **delicate** API. It is easy to accidentally create resource or memory leaks when
144  * `GlobalScope` is used. A coroutine launched in `GlobalScope` is not subject to the principle of structured
145  * concurrency, so if it hangs or gets delayed due to a problem (e.g. due to a slow network), it will stay working
146  * and consuming resources. For example, consider the following code:
147  *
148  * ```
149  * fun loadConfiguration() {
150  *     GlobalScope.launch {
151  *         val config = fetchConfigFromServer() // network request
152  *         updateConfiguration(config)
153  *     }
154  * }
155  * ```
156  *
157  * A call to `loadConfiguration` creates a coroutine in the `GlobalScope` that works in background without any
158  * provision to cancel it or to wait for its completion. If a network is slow, it keeps waiting in background,
159  * consuming resources. Repeated calls to `loadConfiguration` will consume more and more resources.
160  *
161  * ### Possible replacements
162  *
163  * In many cases uses of `GlobalScope` should be removed, marking the containing operation with `suspend`, for example:
164  *
165  * ```
166  * suspend fun loadConfiguration() {
167  *     val config = fetchConfigFromServer() // network request
168  *     updateConfiguration(config)
169  * }
170  * ```
171  *
172  * In cases when `GlobalScope.launch` was used to launch multiple concurrent operations, the corresponding
173  * operations shall be grouped with [coroutineScope] instead:
174  *
175  * ```
176  * // concurrently load configuration and data
177  * suspend fun loadConfigurationAndData() {
178  *     coroutineScope {
179  *         launch { loadConfiguration() }
180  *         launch { loadData() }
181  *     }
182  * }
183  * ```
184  *
185  * In top-level code, when launching a concurrent operation from a non-suspending context, an appropriately
186  * confined instance of [CoroutineScope] shall be used instead of a `GlobalScope`. See docs on [CoroutineScope] for
187  * details.
188  *
189  * ### GlobalScope vs custom scope
190  *
191  * Do not replace `GlobalScope.launch { ... }` with `CoroutineScope().launch { ... }` constructor function call.
192  * The latter has the same pitfalls as `GlobalScope`. See [CoroutineScope] documentation on the intended usage of
193  * `CoroutineScope()` constructor function.
194  *
195  * ### Legitimate use-cases
196  *
197  * There are limited circumstances under which `GlobalScope` can be legitimately and safely used, such as top-level background
198  * processes that must stay active for the whole duration of the application's lifetime. Because of that, any use
199  * of `GlobalScope` requires an explicit opt-in with `@OptIn(DelicateCoroutinesApi::class)`, like this:
200  *
201  * ```
202  * // A global coroutine to log statistics every second, must be always active
203  * @OptIn(DelicateCoroutinesApi::class)
204  * val globalScopeReporter = GlobalScope.launch {
205  *     while (true) {
206  *         delay(1000)
207  *         logStatistics()
208  *     }
209  * }
210  * ```
211  */
212 @DelicateCoroutinesApi
213 public object GlobalScope : CoroutineScope {
214     /**
215      * Returns [EmptyCoroutineContext].
216      */
217     override val coroutineContext: CoroutineContext
218         get() = EmptyCoroutineContext
219 }
220 
221 /**
222  * Creates a [CoroutineScope] and calls the specified suspend block with this scope.
223  * The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, using the
224  * [Job] from that context as the parent for a new [Job].
225  *
226  * This function is designed for _concurrent decomposition_ of work. When any child coroutine in this scope fails,
227  * this scope fails, cancelling all the other children (for a different behavior, see [supervisorScope]).
228  * This function returns as soon as the given block and all its child coroutines are completed.
229  * A usage of a scope looks like this:
230  *
231  * ```
232  * suspend fun showSomeData() = coroutineScope {
233  *     val data = async(Dispatchers.IO) { // <- extension on current scope
234  *      ... load some UI data for the Main thread ...
235  *     }
236  *
237  *     withContext(Dispatchers.Main) {
238  *         doSomeWork()
239  *         val result = data.await()
240  *         display(result)
241  *     }
242  * }
243  * ```
244  *
245  * The scope in this example has the following semantics:
246  * 1) `showSomeData` returns as soon as the data is loaded and displayed in the UI.
247  * 2) If `doSomeWork` throws an exception, then the `async` task is cancelled and `showSomeData` rethrows that exception.
248  * 3) If the outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled.
249  * 4) If the `async` block fails, `withContext` will be cancelled.
250  *
251  * The method may throw a [CancellationException] if the current job was cancelled externally,
252  * rethrow the exception thrown by [block], or throw an unhandled [Throwable] if there is one
253  * (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope).
254  */
coroutineScopenull255 public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
256     contract {
257         callsInPlace(block, InvocationKind.EXACTLY_ONCE)
258     }
259     return suspendCoroutineUninterceptedOrReturn { uCont ->
260         val coroutine = ScopeCoroutine(uCont.context, uCont)
261         coroutine.startUndispatchedOrReturn(coroutine, block)
262     }
263 }
264 
265 /**
266  * Creates a [CoroutineScope] that wraps the given coroutine [context].
267  *
268  * If the given [context] does not contain a [Job] element, then a default `Job()` is created.
269  * This way, failure of any child coroutine in this scope or [cancellation][CoroutineScope.cancel] of the scope itself
270  * cancels all the scope's children, just like inside [coroutineScope] block.
271  */
272 @Suppress("FunctionName")
CoroutineScopenull273 public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
274     ContextScope(if (context[Job] != null) context else context + Job())
275 
276 /**
277  * Cancels this scope, including its job and all its children with an optional cancellation [cause].
278  * A cause can be used to specify an error message or to provide other details on
279  * a cancellation reason for debugging purposes.
280  * Throws [IllegalStateException] if the scope does not have a job in it.
281  */
282 public fun CoroutineScope.cancel(cause: CancellationException? = null) {
283     val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
284     job.cancel(cause)
285 }
286 
287 /**
288  * Cancels this scope, including its job and all its children with a specified diagnostic error [message].
289  * A [cause] can be specified to provide additional details on a cancellation reason for debugging purposes.
290  * Throws [IllegalStateException] if the scope does not have a job in it.
291  */
cancelnull292 public fun CoroutineScope.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause))
293 
294 /**
295  * Ensures that current scope is [active][CoroutineScope.isActive].
296  *
297  * If the job is no longer active, throws [CancellationException].
298  * If the job was cancelled, thrown exception contains the original cancellation cause.
299  * This function does not do anything if there is no [Job] in the scope's [coroutineContext][CoroutineScope.coroutineContext].
300  *
301  * This method is a drop-in replacement for the following code, but with more precise exception:
302  * ```
303  * if (!isActive) {
304  *     throw CancellationException()
305  * }
306  * ```
307  *
308  * @see CoroutineContext.ensureActive
309  */
310 public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()
311 
312 
313 /**
314  * Returns the current [CoroutineContext] retrieved by using [kotlin.coroutines.coroutineContext].
315  * This function is an alias to avoid name clash with [CoroutineScope.coroutineContext] in a receiver position:
316  *
317  * ```
318  * launch { // this: CoroutineScope
319  *     val flow = flow<Unit> {
320  *         coroutineContext // Resolves into the context of outer launch, which is incorrect, see KT-38033
321  *         currentCoroutineContext() // Retrieves actual context where the flow is collected
322  *     }
323  * }
324  * ```
325  */
326 public suspend inline fun currentCoroutineContext(): CoroutineContext = coroutineContext
327