Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark GlobalScope as delicate API and rewrite coroutine basics doc without it #2637

Merged
merged 17 commits into from Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file removed docs/images/new-mvn-project-jvm.png
Binary file not shown.
22 changes: 16 additions & 6 deletions docs/topics/composing-suspending-functions.md
Expand Up @@ -24,7 +24,7 @@ suspend fun doSomethingUsefulTwo(): Int {

What do we do if we need them to be invoked _sequentially_ — first `doSomethingUsefulOne` _and then_
`doSomethingUsefulTwo`, and compute the sum of their results?
In practice we do this if we use the result of the first function to make a decision on whether we need
In practice, we do this if we use the result of the first function to make a decision on whether we need
to invoke the second one or to decide on how to invoke it.

We use a normal sequential invocation, because the code in the coroutine, just like in the regular
Expand Down Expand Up @@ -190,18 +190,26 @@ standard `lazy` function in cases when computation of the value involves suspend
## Async-style functions

We can define async-style functions that invoke `doSomethingUsefulOne` and `doSomethingUsefulTwo`
_asynchronously_ using the [async] coroutine builder with an explicit [GlobalScope] reference.
_asynchronously_ using the [async] coroutine builder using a [GlobalScope] reference to
opt-out of the structured concurrency.
We name such functions with the
"...Async" suffix to highlight the fact that they only start asynchronous computation and one needs
to use the resulting deferred value to get the result.

> [GlobalScope] is a delicate API that can backfire in non-trivial ways, one of which will be explained
> below, so you must explicitly opt-in into using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`.
>
{type="note"}

```kotlin
// The result type of somethingUsefulOneAsync is Deferred<Int>
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}

// The result type of somethingUsefulTwoAsync is Deferred<Int>
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
Expand Down Expand Up @@ -236,10 +244,12 @@ fun main() {
}
//sampleEnd

@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}

@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
Expand Down Expand Up @@ -272,9 +282,9 @@ Completed in 1085 ms
{type="note"}

Consider what happens if between the `val one = somethingUsefulOneAsync()` line and `one.await()` expression there is some logic
error in the code and the program throws an exception and the operation that was being performed by the program aborts.
error in the code, and the program throws an exception, and the operation that was being performed by the program aborts.
Normally, a global error-handler could catch this exception, log and report the error for developers, but the program
could otherwise continue doing other operations. But here we have `somethingUsefulOneAsync` still running in the background,
could otherwise continue doing other operations. However, here we have `somethingUsefulOneAsync` still running in the background,
even though the operation that initiated it was aborted. This problem does not happen with structured
concurrency, as shown in the section below.

Expand All @@ -293,7 +303,7 @@ suspend fun concurrentSum(): Int = coroutineScope {
}
```

This way, if something goes wrong inside the code of the `concurrentSum` function and it throws an exception,
This way, if something goes wrong inside the code of the `concurrentSum` function, and it throws an exception,
all the coroutines that were launched in its scope will be cancelled.

<!--- CLEAR -->
Expand Down Expand Up @@ -403,4 +413,4 @@ Computation failed with ArithmeticException
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html

<!--- END -->
<!--- END -->
26 changes: 15 additions & 11 deletions docs/topics/coroutine-context-and-dispatchers.md
Expand Up @@ -65,9 +65,8 @@ context of the main `runBlocking` coroutine which runs in the `main` thread.
[Dispatchers.Unconfined] is a special dispatcher that also appears to run in the `main` thread, but it is,
in fact, a different mechanism that is explained later.

The default dispatcher that is used when coroutines are launched in [GlobalScope]
is represented by [Dispatchers.Default] and uses a shared background pool of threads,
so `launch(Dispatchers.Default) { ... }` uses the same dispatcher as `GlobalScope.launch { ... }`.
The default dispatcher that is used when no other dispatcher is explicitly specified in the scope.
It is represented by [Dispatchers.Default] and uses a shared background pool of threads.

[newSingleThreadContext] creates a thread for the coroutine to run.
A dedicated thread is a very expensive resource.
Expand Down Expand Up @@ -303,8 +302,14 @@ the [Job] of the new coroutine becomes
a _child_ of the parent coroutine's job. When the parent coroutine is cancelled, all its children
are recursively cancelled, too.

However, when [GlobalScope] is used to launch a coroutine, there is no parent for the job of the new coroutine.
It is therefore not tied to the scope it was launched from and operates independently.
However, this parent-child relation can be explicitly overriden in one of two ways:

1. When a different scope is explicitly specified when launching a coroutine (for example, `GlobalScope.launch`),
then it does not inherit a `Job` from the parent scope.
2. When a different `Job` object is passed as the context for the new coroutine (as show in the example below),
then it overrides the `Job` of the parent scope.

In both cases, the launched coroutine is not tied to the scope it was launched from and operates independently.

```kotlin
import kotlinx.coroutines.*
Expand All @@ -313,9 +318,9 @@ fun main() = runBlocking<Unit> {
//sampleStart
// launch a coroutine to process some kind of incoming request
val request = launch {
// it spawns two other jobs, one with GlobalScope
GlobalScope.launch {
println("job1: I run in GlobalScope and execute independently!")
// it spawns two other jobs
launch(Job()) {
println("job1: I run in my own Job and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
}
Expand Down Expand Up @@ -343,7 +348,7 @@ fun main() = runBlocking<Unit> {
The output of this code is:

```text
job1: I run in GlobalScope and execute independently!
job1: I run in my own Job and execute independently!
job2: I am a child of the request coroutine
job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?
Expand Down Expand Up @@ -659,7 +664,6 @@ that should be implemented.
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
[ExecutorCoroutineDispatcher.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-executor-coroutine-dispatcher/close.html
Expand All @@ -678,4 +682,4 @@ that should be implemented.
[ensurePresent]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/ensure-present.html
[ThreadContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html

<!--- END -->
<!--- END -->