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