├── settings.gradle
├── docs
├── params.json
├── direct-style.png
├── coroutines-intro-1.png
├── coroutines-intro-2.png
├── coroutines-intro-3.png
├── coroutines-intro-4.png
├── _includes
│ ├── page-header.html
│ ├── page-footer.html
│ └── head.html
├── continuation-passing-style.png
├── _layouts
│ ├── post.html
│ └── default.html
├── async-await.md
├── _config.yml
├── state-machine.md
├── css
│ ├── github-light.css
│ ├── normalize.css
│ └── cayman.css
└── index.md
├── src
├── main
│ ├── resources
│ │ ├── application.properties
│ │ ├── simplelogger.properties
│ │ └── logback.xml
│ └── kotlin
│ │ └── org
│ │ └── pedrofelix
│ │ └── kotlin
│ │ └── examples
│ │ ├── buildstuff18.kt
│ │ └── spring-examples.kt
└── test
│ └── kotlin
│ └── org
│ └── pedrofelix
│ └── kotlin
│ └── examples
│ ├── RxExampleTests.kt
│ ├── SynchronousExceptionExamples.kt
│ ├── RandomTests.kt
│ ├── MoreScopeExamples.kt
│ └── ScopeExamples.kt
├── README.md
├── gradle
└── wrapper
│ └── gradle-wrapper.properties
├── .gitignore
├── gradlew.bat
├── gradlew
└── LICENSE
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'coroutines'
2 |
3 |
--------------------------------------------------------------------------------
/docs/params.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Kotlin Coroutines"
3 | }
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.mvc.async.request-timeout=5000
--------------------------------------------------------------------------------
/docs/direct-style.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmhsfelix/kotlin-coroutines/HEAD/docs/direct-style.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # kotlin-coroutines
2 |
3 | Set of examples using Kotlin coroutines, mainly for education purposes.
4 |
--------------------------------------------------------------------------------
/docs/coroutines-intro-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmhsfelix/kotlin-coroutines/HEAD/docs/coroutines-intro-1.png
--------------------------------------------------------------------------------
/docs/coroutines-intro-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmhsfelix/kotlin-coroutines/HEAD/docs/coroutines-intro-2.png
--------------------------------------------------------------------------------
/docs/coroutines-intro-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmhsfelix/kotlin-coroutines/HEAD/docs/coroutines-intro-3.png
--------------------------------------------------------------------------------
/docs/coroutines-intro-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmhsfelix/kotlin-coroutines/HEAD/docs/coroutines-intro-4.png
--------------------------------------------------------------------------------
/docs/_includes/page-header.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/continuation-passing-style.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmhsfelix/kotlin-coroutines/HEAD/docs/continuation-passing-style.png
--------------------------------------------------------------------------------
/docs/_layouts/post.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
{{ page.title }}
5 | {{ page.date | date_to_string }}
6 |
7 | {{ content }}
--------------------------------------------------------------------------------
/src/main/resources/simplelogger.properties:
--------------------------------------------------------------------------------
1 | org.slf4j.simpleLogger.showDateTime=true
2 | org.slf4j.simpleLogger.showShortLogName=true
3 | org.slf4j.simpleLogger.defaultLogLevel=info
4 |
--------------------------------------------------------------------------------
/docs/async-await.md:
--------------------------------------------------------------------------------
1 | # Coroutines and CompletableFuture #
2 |
3 | Add the `kotlinx-coroutines-jdk8` library
4 |
5 | ```gradle
6 | compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-jdk8', version: '0.21.2'
7 | ```
8 |
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jun 09 15:41:57 WEST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-all.zip
7 |
--------------------------------------------------------------------------------
/docs/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% include head.html %}
5 |
6 |
7 | {% include page-header.html %}
8 |
9 |
10 |
11 | {{ content }}
12 |
13 | {% include page-footer.html %}
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | # Site settings
2 | title: Kotlin Coroutines
3 | tag_text: Kotlin Coroutines
4 | description: Kotlin Coroutines
5 | url: "https://labs.pedrofelix.org"
6 | baseurl: "/kotlin-coroutines"
7 | author:
8 | url: "https://labs.pedrofelix.org"
9 | name: "Pedro Felix"
10 |
11 | font-awesome-include: true # make this false if you don't need font-awesome
12 | highlighter: rouge
13 |
14 | exclude: ["README.md"]
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.ear
17 | *.zip
18 | *.tar.gz
19 | *.rar
20 |
21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
22 | hs_err_pid*
23 |
24 | .idea
25 | .gradle
26 | *.iml
27 | out
28 | build
29 |
--------------------------------------------------------------------------------
/docs/_includes/page-footer.html:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/test/kotlin/org/pedrofelix/kotlin/examples/RxExampleTests.kt:
--------------------------------------------------------------------------------
1 | package org.pedrofelix.kotlin.examples
2 |
3 | import kotlinx.coroutines.GlobalScope
4 | import kotlinx.coroutines.delay
5 | import kotlinx.coroutines.launch
6 | import org.junit.Test
7 | import rx.Single
8 |
9 | class RxExampleTests {
10 |
11 | @Test
12 | fun test() {
13 |
14 | val s = Single.create { obs ->
15 | val job = GlobalScope.launch {
16 | delay(1000)
17 | obs.onSuccess("done")
18 | }
19 | // ...
20 | job.cancel()
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/test/kotlin/org/pedrofelix/kotlin/examples/SynchronousExceptionExamples.kt:
--------------------------------------------------------------------------------
1 | package org.pedrofelix.kotlin.examples
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.GlobalScope
5 | import kotlinx.coroutines.launch
6 | import org.junit.Assert.assertTrue
7 | import org.junit.Test
8 | import org.slf4j.LoggerFactory
9 |
10 | private val log = LoggerFactory.getLogger(SynchronousExceptionExamples::class.java)
11 |
12 | class SynchronousExceptionExamples {
13 | @Test
14 | fun example() {
15 | log.info("before launch")
16 | val job = GlobalScope.launch(Dispatchers.Unconfined) {
17 | log.info("before throw")
18 | throw Exception("an-error")
19 | }
20 | log.info("after launch")
21 | Thread.sleep(1000)
22 | assertTrue(job.isCancelled)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/kotlin/org/pedrofelix/kotlin/examples/RandomTests.kt:
--------------------------------------------------------------------------------
1 | package org.pedrofelix.kotlin.examples
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Test
5 | import org.pedrofelix.experiments.ScopeExamples
6 | import org.slf4j.LoggerFactory
7 | import kotlin.coroutines.ContinuationInterceptor
8 | import kotlin.coroutines.coroutineContext
9 |
10 | private val log = LoggerFactory.getLogger(RandomTests::class.java)
11 |
12 | class RandomTests {
13 |
14 | private suspend fun doSomethingWith(i: Int) {
15 | log.info("ctx {}", coroutineContext)
16 | }
17 |
18 | @Test
19 | fun first() {
20 | val items = listOf(1,2,3,4)
21 | runBlocking {
22 | withContext(Dispatchers.IO) {
23 | log.info("ctx {}", coroutineContext)
24 | items.forEach { launch { doSomethingWith(it) } }
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docs/_includes/head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ site.title }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/state-machine.md:
--------------------------------------------------------------------------------
1 | # Suspending functions, coroutines and state machines #
2 |
3 | We started this guide referring that Kotlin uses coroutines to address asynchronicy issues, however the mechanism presented in the previous section is called *suspending functions*.
4 | So, what is the relation between these *suspending functions* and coroutines?
5 | And how is it possible for a thread to leave a suspending function while in the middle of it, without a return in sight.
6 | A look into how suspending functions are compiled provides the necessary knowledge to answer these questions.
7 |
8 | The code generated by the Kotlin compiler for suspending functions uses a method named [*continuation passing style*](https://en.wikipedia.org/wiki/Continuation-passing_style).
9 | On the normal *direct style*, when a function ends it returns to its call site, typically be assigning the program counter with the value saved in the stack.
10 | On the continuation-passing style, the code to execute after a function ends is passed in to the function as an extra parameter, called the *continuation*
11 |
12 | For instance, consider the following pseudo-code
13 |
14 | ```
15 | prev_statement
16 | function(a,b)
17 | next_statement
18 | ...
19 | ````
20 |
21 | Using a direct passing style, the call can be represented with the following diagram
22 |
23 | 
24 |
25 | Using the continuation-passing style, the code after the `function` call is encapsulated in a function, which is passed in the call to `function`.
26 |
27 | 
28 |
29 | As an example, the generated code for the following suspending function.
30 | Instead of,
31 | ```kotlin
32 | suspend fun suspendFunctionWithDelay(a: Int, b: Int): Int
33 | ```
34 |
35 | has the following Java-equivalent signature.
36 |
37 | ```java
38 | public static final Object suspendFunctionWithDelay(int a, int b, @NotNull Continuation var2) {
39 | ```
40 |
41 | where `Continuation` is an interface with the following signature
42 |
43 | ```kotlin
44 | public interface Continuation {
45 | public fun resume(value: T)
46 | public fun resumeWithException(exception: Throwable)
47 | (...)
48 | }
49 | ```
50 |
51 | Note that this is the same `Continuation` interface used in the `suspendCoroutine` described in the previous section.
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/docs/css/github-light.css:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 GitHub Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | */
17 |
18 | .pl-c /* comment */ {
19 | color: #969896;
20 | }
21 |
22 | .pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */,
23 | .pl-s .pl-v /* string variable */ {
24 | color: #0086b3;
25 | }
26 |
27 | .pl-e /* entity */,
28 | .pl-en /* entity.name */ {
29 | color: #795da3;
30 | }
31 |
32 | .pl-s .pl-s1 /* string source */,
33 | .pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ {
34 | color: #333;
35 | }
36 |
37 | .pl-ent /* entity.name.tag */ {
38 | color: #63a35c;
39 | }
40 |
41 | .pl-k /* keyword, storage, storage.type */ {
42 | color: #a71d5d;
43 | }
44 |
45 | .pl-pds /* punctuation.definition.string, string.regexp.character-class */,
46 | .pl-s /* string */,
47 | .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */,
48 | .pl-sr /* string.regexp */,
49 | .pl-sr .pl-cce /* string.regexp constant.character.escape */,
50 | .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */,
51 | .pl-sr .pl-sre /* string.regexp source.ruby.embedded */ {
52 | color: #183691;
53 | }
54 |
55 | .pl-v /* variable */ {
56 | color: #ed6a43;
57 | }
58 |
59 | .pl-id /* invalid.deprecated */ {
60 | color: #b52a1d;
61 | }
62 |
63 | .pl-ii /* invalid.illegal */ {
64 | background-color: #b52a1d;
65 | color: #f8f8f8;
66 | }
67 |
68 | .pl-sr .pl-cce /* string.regexp constant.character.escape */ {
69 | color: #63a35c;
70 | font-weight: bold;
71 | }
72 |
73 | .pl-ml /* markup.list */ {
74 | color: #693a17;
75 | }
76 |
77 | .pl-mh /* markup.heading */,
78 | .pl-mh .pl-en /* markup.heading entity.name */,
79 | .pl-ms /* meta.separator */ {
80 | color: #1d3e81;
81 | font-weight: bold;
82 | }
83 |
84 | .pl-mq /* markup.quote */ {
85 | color: #008080;
86 | }
87 |
88 | .pl-mi /* markup.italic */ {
89 | color: #333;
90 | font-style: italic;
91 | }
92 |
93 | .pl-mb /* markup.bold */ {
94 | color: #333;
95 | font-weight: bold;
96 | }
97 |
98 | .pl-md /* markup.deleted, meta.diff.header.from-file */ {
99 | background-color: #ffecec;
100 | color: #bd2c00;
101 | }
102 |
103 | .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ {
104 | background-color: #eaffea;
105 | color: #55a532;
106 | }
107 |
108 | .pl-mdr /* meta.diff.range */ {
109 | color: #795da3;
110 | font-weight: bold;
111 | }
112 |
113 | .pl-mo /* meta.output */ {
114 | color: #1d3e81;
115 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save ( ) {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/src/test/kotlin/org/pedrofelix/kotlin/examples/MoreScopeExamples.kt:
--------------------------------------------------------------------------------
1 | package org.pedrofelix.kotlin.examples
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Test
5 | import org.slf4j.LoggerFactory
6 | import java.util.concurrent.atomic.AtomicInteger
7 |
8 | private val log = LoggerFactory.getLogger(MoreScopeExamples::class.java)
9 |
10 | class MoreScopeExamples {
11 |
12 | @Test
13 | fun example0() {
14 | val res = Array(4 + 2) { Result() }
15 | runBlocking {
16 | log.info("[1] start")
17 |
18 | launchEx(2) {
19 | completeAfter(300, 2, res)
20 | }
21 |
22 | launchEx(3) {
23 | launchEx(4) {
24 | completeAfter(1000, 4, res)
25 | }
26 | launchEx(5) {
27 | completeAfter(200, 5, res)
28 | }
29 | log.info("after launching [4] and [5]")
30 | completeAfter(500, 3, res)
31 | }
32 | log.info("[1] end")
33 | }
34 | log.info("complete")
35 | }
36 |
37 | @Test
38 | // [5] completes and then [2] throws ...
39 | fun example1() {
40 | val res = Array(4 + 2) { Result() }
41 | runBlocking {
42 | log.info("[1] start")
43 |
44 | launchEx(2) {
45 | throwAfter(300, 2, res)
46 | }
47 |
48 | launchEx(3) {
49 | launchEx(4) {
50 | completeAfter(1000, 4, res)
51 | }
52 | launchEx(5) {
53 | completeAfter(200, 5, res)
54 | }
55 | log.info("after launching [4] and [5]")
56 | completeAfter(500, 3, res)
57 | }
58 | log.info("[1] end")
59 | }
60 | log.info("complete")
61 | }
62 |
63 | @Test
64 | // [5] throws
65 | fun example2() {
66 | val res = Array(4 + 2) { Result() }
67 | runBlocking {
68 | log.info("[1] start")
69 |
70 | launchEx(2) {
71 | completeAfter(300, 2, res)
72 | }
73 |
74 | launchEx(3) {
75 | launchEx(4) {
76 | completeAfter(1000, 4, res)
77 | }
78 | launchEx(5) {
79 | throwAfter(200, 5, res)
80 | }
81 | completeAfter(500, 3, res)
82 | }
83 | log.info("after launching [4] and [5]")
84 | log.info("[1] end")
85 | }
86 | log.info("complete")
87 | }
88 |
89 | @Test
90 | // with a coroutineScope
91 | fun example3() {
92 | val res = Array(4 + 2) { Result() }
93 | runBlocking {
94 | log.info("[1] start")
95 |
96 | launchEx(2) {
97 | completeAfter(300, 2, res)
98 | }
99 |
100 | launchEx(3) {
101 | coroutineScope {
102 | launchEx(4) {
103 | completeAfter(1000, 4, res)
104 | }
105 | launchEx(5) {
106 | completeAfter(200, 5, res)
107 | }
108 | }
109 | log.info("after coroutineScope with [4] and [5]")
110 | completeAfter(500, 3, res)
111 | }
112 | log.info("after launching [4] and [5]")
113 | log.info("[1] end")
114 | }
115 | log.info("complete")
116 | }
117 |
118 | @Test
119 | // with a coroutineScope and a failure
120 | fun example4() {
121 | val res = Array(4 + 2) { Result() }
122 | runBlocking {
123 | log.info("[1] start")
124 |
125 | launchEx(2) {
126 | completeAfter(300, 2, res)
127 | }
128 |
129 | launchEx(3) {
130 | coroutineScope {
131 | launchEx(4) {
132 | completeAfter(1000, 4, res)
133 | }
134 | launchEx(5) {
135 | throwAfter(200, 5, res)
136 | }
137 | }
138 | log.info("after coroutineScope with [4] and [5]")
139 | completeAfter(500, 3, res)
140 | }
141 | log.info("after launching [4] and [5]")
142 | log.info("[1] end")
143 | }
144 | log.info("complete")
145 | }
146 |
147 | @Test
148 | // with a supervisorScope and a failure
149 | fun examplet() {
150 | val res = Array(4 + 2) { Result() }
151 | runBlocking {
152 | log.info("[1] start")
153 |
154 | launchEx(2) {
155 | completeAfter(300, 2, res)
156 | }
157 |
158 | launchEx(3) {
159 | supervisorScope {
160 | launchEx(4) {
161 | completeAfter(1000, 4, res)
162 | }
163 | launchEx(5) {
164 | throwAfter(200, 5, res)
165 | }
166 | }
167 | log.info("after supervisorScope with [4] and [5]")
168 | completeAfter(500, 3, res)
169 | }
170 | log.info("after launching [4] and [5]")
171 | log.info("[1] end")
172 | }
173 | log.info("complete")
174 | }
175 |
176 | fun CoroutineScope.launchEx(ix: Int, block: suspend CoroutineScope.() -> Unit) = launch(block=block).apply {
177 | this.invokeOnCompletion { cause ->
178 | log.info("[{}] completed with '{}'", ix, cause?.message ?: "success")
179 | }
180 | }
181 |
182 | suspend fun completeAfter(ms: Long, ix: Int, res: Array) {
183 | try {
184 | delay(ms)
185 | } catch (ex: CancellationException) {
186 | log.warn("[{}] delay ended with CancellationException '{}'", ix, ex.message)
187 | res[ix].completeWithCancellation()
188 | return
189 | }
190 | log.info("[{}] completing", ix)
191 | res[ix].complete()
192 | }
193 |
194 | suspend fun throwAfter(ms: Long, ix: Int, res: Array) {
195 | try {
196 | delay(ms)
197 | } catch (ex: CancellationException) {
198 | log.warn("[{}] delay ended with CancellationException '{}'", ix, ex.message)
199 | res[ix].completeWithCancellation()
200 | return
201 | }
202 | log.info("[{}] throwing", ix)
203 | res[ix].completeWithError()
204 | throw Exception("coroutine ${ix} ending with error")
205 | }
206 |
207 | class Result {
208 | private val state = AtomicInteger(0)
209 | fun complete() = state.set(1)
210 | fun completeWithCancellation() = state.set(2)
211 | fun completeWithError() = state.set(3)
212 | fun isCompleted() = state.get() != 0
213 | fun isSuccess() = state.get() == 1
214 | fun isCancelled() = state.get() == 2
215 | fun isError() = state.get() == 3
216 | }
217 | }
--------------------------------------------------------------------------------
/src/main/kotlin/org/pedrofelix/kotlin/examples/buildstuff18.kt:
--------------------------------------------------------------------------------
1 | // Introductory examples for the Kotlin Coroutines talk on BuildStuff 18
2 |
3 | package org.pedrofelix.kotlin.examples
4 |
5 | import kotlinx.coroutines.*
6 | import kotlinx.coroutines.Dispatchers.Unconfined
7 | import kotlinx.coroutines.future.await
8 | import kotlinx.coroutines.future.future
9 | import org.slf4j.LoggerFactory
10 | import java.util.concurrent.CompletableFuture
11 | import java.util.concurrent.Executors
12 | import java.util.concurrent.TimeUnit
13 | import kotlin.coroutines.*
14 |
15 | private val log = LoggerFactory.getLogger("intro")
16 |
17 | fun blockingOperation(input: Int): Int {
18 | Thread.sleep(1000)
19 | return input + 42
20 | }
21 |
22 | private val executor = Executors.newScheduledThreadPool(5)
23 | fun asyncOperationWithCallback(input: Int, callback: (Int) -> Unit): Unit {
24 | executor.schedule({ callback(input + 42) }, 1000, TimeUnit.MILLISECONDS)
25 | }
26 |
27 | fun asyncOperationReturningFuture(input: Int) = CompletableFuture().apply {
28 | val scheduledFuture = executor.schedule({ this.complete(input + 42) }, 1000, TimeUnit.MILLISECONDS)
29 | this.handle {_, ex ->
30 | if(this.isCancelled){
31 | scheduledFuture.cancel(true)
32 | }
33 | }
34 | }
35 |
36 | fun doSomethingWith(input: Int): Int {
37 | log.info("doing something with {}", input)
38 | return input + 1
39 | }
40 |
41 | val `?` = 0
42 |
43 | fun example0(input: Int): Int {
44 | var acc = input
45 | for (i in 0..2) {
46 | val result = blockingOperation(acc)
47 | acc = doSomethingWith(result)
48 | }
49 | return acc
50 | }
51 |
52 | fun example1(input: Int): Int {
53 | var acc = input
54 | for (i in 0..2) {
55 | asyncOperationWithCallback(acc) { result -> `?` }
56 | acc = doSomethingWith(`?`)
57 | }
58 | return acc
59 | }
60 |
61 | fun example2(input: Int): Int {
62 | var acc = input
63 | for (i in 0..2) {
64 | asyncOperationWithCallback(acc) { result ->
65 | // different scope
66 | // outside of for loop
67 | acc = doSomethingWith(result)
68 | }
69 | // acc = doSomethingWith(result)
70 | }
71 | return acc
72 | }
73 |
74 | fun example3(input: Int): Int {
75 | var acc = input
76 | for (i in 0..2) {
77 | val future = asyncOperationReturningFuture(acc)
78 | acc = doSomethingWith(future.get()) // now it's blocking again
79 | }
80 | return acc
81 | }
82 |
83 | fun example4(input: Int): Int {
84 | var acc = input
85 | for (i in 0..2) {
86 | val future = asyncOperationReturningFuture(acc)
87 | future.thenAccept { result ->
88 | // same issue than with callbacks
89 | acc = doSomethingWith(result)
90 | }
91 | }
92 | return acc
93 | }
94 |
95 | suspend fun example5(input: Int): Int {
96 | var acc = input
97 | for (i in 0..2) {
98 | val future = asyncOperationReturningFuture(acc)
99 | val result = suspendCoroutine { continuation ->
100 | future.thenAccept { result ->
101 | continuation.resume(result)
102 | }
103 | }
104 | acc = doSomethingWith(result)
105 | }
106 | return acc
107 | }
108 |
109 | /*
110 | suspend fun example5(input: Int): CompletableFuture {
111 | var acc = input
112 | for (i in 0..2) {
113 | val result = await asyncOperationReturningFuture(acc)
114 | acc = doSomethingWith(result)
115 | }
116 | return acc
117 | }
118 | */
119 |
120 | private suspend fun CompletableFuture.await2(): T = suspendCoroutine { cont ->
121 | whenComplete { value, error ->
122 | if (error != null)
123 | cont.resumeWithException(error)
124 | else
125 | cont.resume(value)
126 | }
127 | }
128 |
129 | suspend fun example6(input: Int): Int {
130 | var acc = input
131 | for (i in 0..2) {
132 | val future = asyncOperationReturningFuture(acc)
133 | val result = future.await()
134 | acc = doSomethingWith(result)
135 | }
136 | return acc
137 | }
138 |
139 | suspend fun example7(input: Int): Int {
140 | var acc = input
141 | for (i in 0..2) {
142 | log.info("before asyncOperationReturningFuture")
143 | val result = asyncOperationReturningFuture(acc).await()
144 | log.info("after asyncOperationReturningFuture")
145 | acc = doSomethingWith(result)
146 | }
147 | return acc
148 | }
149 |
150 | suspend fun composingSuspendFunctions(input: Int): Int {
151 | var acc = 0
152 | for (i in 0..2) {
153 | acc = example7(acc)
154 | }
155 | return acc
156 | }
157 |
158 | suspend fun example8(input: Int): Int {
159 | var acc = input
160 | for (i in 0..2) {
161 | val result = suspendCoroutine { cont ->
162 | asyncOperationWithCallback(acc) {
163 | cont.resume(it)
164 | }
165 | }
166 | acc = doSomethingWith(result)
167 | }
168 | return acc
169 | }
170 |
171 | class StateMachine(input: Int, val continuation: Continuation) {
172 | var acc = input
173 | var i = 0
174 | var state = 0
175 | var result = 0
176 |
177 | fun start() {
178 | state = 0
179 | run()
180 | }
181 |
182 | private fun run() {
183 | while (true) {
184 | when (state) {
185 | 0 -> {
186 | if (i > 2) {
187 | continuation.resume(acc)
188 | return
189 | }
190 | state = 1
191 | }
192 | 1 -> {
193 | asyncOperationReturningFuture(acc).thenAccept { res ->
194 | result = res
195 | state = 2
196 | run()
197 | }
198 | return;
199 | }
200 | 2 -> {
201 | acc = doSomethingWith(result)
202 | i += 1
203 | state = 0
204 | }
205 | }
206 | }
207 | }
208 | }
209 |
210 | fun main(args: Array) {
211 | //usingExample0()
212 | //usingAStateMachine()
213 | //usingASuspendFunction5()
214 | runBlocking {
215 | async(Dispatchers.Unconfined) {
216 | example7(0)
217 | }
218 | }
219 |
220 | Thread.sleep(4000)
221 | executor.shutdown()
222 | }
223 |
224 | private fun usingExample0() {
225 | log.info("result is {}", example0(1))
226 | }
227 |
228 | private fun usingASuspendFunction() {
229 | // example7(1)
230 | // Error: Suspend function '...' should be called only from a coroutine or another suspend function
231 | ::example7.startCoroutine(1, object : Continuation {
232 | override fun resumeWith(result: Result) {
233 | log.info("result is {}", result.getOrThrow())
234 | }
235 |
236 | override val context: CoroutineContext
237 | get() = Unconfined
238 | })
239 | }
240 |
241 | private fun usingASuspendFunction2() {
242 | val future = CompletableFuture()
243 | ::example7.startCoroutine(1, object : Continuation {
244 | override fun resumeWith(result: Result) {
245 | result
246 | .onSuccess { future.complete(it) }
247 | .onFailure { future.completeExceptionally(it) }
248 | }
249 |
250 | override val context: CoroutineContext
251 | get() = Unconfined
252 | })
253 | future.thenApply { log.info("result is {}", it) }
254 | }
255 |
256 | private fun usingASuspendFunction3() {
257 | val deferred = GlobalScope.async {
258 | example7(1)
259 | }
260 | deferred.invokeOnCompletion {
261 | object : CompletionHandler {
262 | override fun invoke(cause: Throwable?) {
263 | log.info("result is {}", deferred.getCompleted())
264 | }
265 | }
266 | }
267 | }
268 |
269 | private fun usingASuspendFunction4() {
270 | val future = CompletableFuture()
271 | val deferred = GlobalScope.async {
272 | try {
273 | future.complete(example7(1))
274 | } catch (ex: Throwable) {
275 | future.completeExceptionally(ex)
276 | }
277 | }
278 | future.thenApply { log.info("result is {}", it) }
279 | }
280 |
281 | private fun usingASuspendFunction5() {
282 | val future = GlobalScope.future {
283 | example7(1)
284 | }
285 | future.thenApply { log.info("result is {}", it) }
286 | }
287 |
288 | private fun usingAStateMachine() {
289 | val continuation = object : Continuation {
290 | override fun resumeWith(result: Result) {
291 | log.info("result is {}", result.getOrThrow())
292 | }
293 |
294 | override val context: CoroutineContext
295 | get() = TODO("not implemented")
296 | }
297 | val stateMachine = StateMachine(1, continuation)
298 | stateMachine.start()
299 | Thread.sleep(4000)
300 | }
301 |
302 |
303 |
--------------------------------------------------------------------------------
/docs/css/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 1.5em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | box-sizing: content-box;
213 | height: 0;
214 | }
215 |
216 | /**
217 | * Contain overflow in all browsers.
218 | */
219 |
220 | pre {
221 | overflow: auto;
222 | }
223 |
224 | /**
225 | * Address odd `em`-unit font size rendering in all browsers.
226 | */
227 |
228 | code,
229 | kbd,
230 | pre,
231 | samp {
232 | font-family: monospace, monospace;
233 | font-size: 1em;
234 | }
235 |
236 | /* Forms
237 | ========================================================================== */
238 |
239 | /**
240 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
241 | * styling of `select`, unless a `border` property is set.
242 | */
243 |
244 | /**
245 | * 1. Correct color not being inherited.
246 | * Known issue: affects color of disabled elements.
247 | * 2. Correct font properties not being inherited.
248 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
249 | */
250 |
251 | button,
252 | input,
253 | optgroup,
254 | select,
255 | textarea {
256 | color: inherit; /* 1 */
257 | font: inherit; /* 2 */
258 | margin: 0; /* 3 */
259 | }
260 |
261 | /**
262 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
263 | */
264 |
265 | button {
266 | overflow: visible;
267 | }
268 |
269 | /**
270 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
271 | * All other form control elements do not inherit `text-transform` values.
272 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
273 | * Correct `select` style inheritance in Firefox.
274 | */
275 |
276 | button,
277 | select {
278 | text-transform: none;
279 | }
280 |
281 | /**
282 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
283 | * and `video` controls.
284 | * 2. Correct inability to style clickable `input` types in iOS.
285 | * 3. Improve usability and consistency of cursor style between image-type
286 | * `input` and others.
287 | */
288 |
289 | button,
290 | html input[type="button"], /* 1 */
291 | input[type="reset"],
292 | input[type="submit"] {
293 | -webkit-appearance: button; /* 2 */
294 | cursor: pointer; /* 3 */
295 | }
296 |
297 | /**
298 | * Re-set default cursor for disabled elements.
299 | */
300 |
301 | button[disabled],
302 | html input[disabled] {
303 | cursor: default;
304 | }
305 |
306 | /**
307 | * Remove inner padding and border in Firefox 4+.
308 | */
309 |
310 | button::-moz-focus-inner,
311 | input::-moz-focus-inner {
312 | border: 0;
313 | padding: 0;
314 | }
315 |
316 | /**
317 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
318 | * the UA stylesheet.
319 | */
320 |
321 | input {
322 | line-height: normal;
323 | }
324 |
325 | /**
326 | * It's recommended that you don't attempt to style these elements.
327 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
328 | *
329 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
330 | * 2. Remove excess padding in IE 8/9/10.
331 | */
332 |
333 | input[type="checkbox"],
334 | input[type="radio"] {
335 | box-sizing: border-box; /* 1 */
336 | padding: 0; /* 2 */
337 | }
338 |
339 | /**
340 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
341 | * `font-size` values of the `input`, it causes the cursor style of the
342 | * decrement button to change from `default` to `text`.
343 | */
344 |
345 | input[type="number"]::-webkit-inner-spin-button,
346 | input[type="number"]::-webkit-outer-spin-button {
347 | height: auto;
348 | }
349 |
350 | /**
351 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
352 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
353 | * (include `-moz` to future-proof).
354 | */
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; /* 1 */ /* 2 */
358 | box-sizing: content-box;
359 | }
360 |
361 | /**
362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | * Safari (but not Chrome) clips the cancel button when the search input has
364 | * padding (and `textfield` appearance).
365 | */
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | /**
373 | * Define consistent border, margin, and padding.
374 | */
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | /**
383 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | */
386 |
387 | legend {
388 | border: 0; /* 1 */
389 | padding: 0; /* 2 */
390 | }
391 |
392 | /**
393 | * Remove default vertical scrollbar in IE 8/9/10/11.
394 | */
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | /**
401 | * Don't inherit the `font-weight` (applied by a rule above).
402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | */
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | /* Tables
410 | ========================================================================== */
411 |
412 | /**
413 | * Remove most spacing between table cells.
414 | */
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
--------------------------------------------------------------------------------
/docs/css/cayman.css:
--------------------------------------------------------------------------------
1 | .highlight table td { padding: 5px; }
2 |
3 | .highlight table pre { margin: 0; }
4 |
5 | .highlight .cm { color: #999988; font-style: italic; }
6 |
7 | .highlight .cp { color: #999999; font-weight: bold; }
8 |
9 | .highlight .c1 { color: #999988; font-style: italic; }
10 |
11 | .highlight .cs { color: #999999; font-weight: bold; font-style: italic; }
12 |
13 | .highlight .c, .highlight .cd { color: #999988; font-style: italic; }
14 |
15 | .highlight .err { color: #a61717; background-color: #e3d2d2; }
16 |
17 | .highlight .gd { color: #000000; background-color: #ffdddd; }
18 |
19 | .highlight .ge { color: #000000; font-style: italic; }
20 |
21 | .highlight .gr { color: #aa0000; }
22 |
23 | .highlight .gh { color: #999999; }
24 |
25 | .highlight .gi { color: #000000; background-color: #ddffdd; }
26 |
27 | .highlight .go { color: #888888; }
28 |
29 | .highlight .gp { color: #555555; }
30 |
31 | .highlight .gs { font-weight: bold; }
32 |
33 | .highlight .gu { color: #aaaaaa; }
34 |
35 | .highlight .gt { color: #aa0000; }
36 |
37 | .highlight .kc { color: #000000; font-weight: bold; }
38 |
39 | .highlight .kd { color: #000000; font-weight: bold; }
40 |
41 | .highlight .kn { color: #000000; font-weight: bold; }
42 |
43 | .highlight .kp { color: #000000; font-weight: bold; }
44 |
45 | .highlight .kr { color: #000000; font-weight: bold; }
46 |
47 | .highlight .kt { color: #445588; font-weight: bold; }
48 |
49 | .highlight .k, .highlight .kv { color: #000000; font-weight: bold; }
50 |
51 | .highlight .mf { color: #009999; }
52 |
53 | .highlight .mh { color: #009999; }
54 |
55 | .highlight .il { color: #009999; }
56 |
57 | .highlight .mi { color: #009999; }
58 |
59 | .highlight .mo { color: #009999; }
60 |
61 | .highlight .m, .highlight .mb, .highlight .mx { color: #009999; }
62 |
63 | .highlight .sb { color: #d14; }
64 |
65 | .highlight .sc { color: #d14; }
66 |
67 | .highlight .sd { color: #d14; }
68 |
69 | .highlight .s2 { color: #d14; }
70 |
71 | .highlight .se { color: #d14; }
72 |
73 | .highlight .sh { color: #d14; }
74 |
75 | .highlight .si { color: #d14; }
76 |
77 | .highlight .sx { color: #d14; }
78 |
79 | .highlight .sr { color: #009926; }
80 |
81 | .highlight .s1 { color: #d14; }
82 |
83 | .highlight .ss { color: #990073; }
84 |
85 | .highlight .s { color: #d14; }
86 |
87 | .highlight .na { color: #008080; }
88 |
89 | .highlight .bp { color: #999999; }
90 |
91 | .highlight .nb { color: #0086B3; }
92 |
93 | .highlight .nc { color: #445588; font-weight: bold; }
94 |
95 | .highlight .no { color: #008080; }
96 |
97 | .highlight .nd { color: #3c5d5d; font-weight: bold; }
98 |
99 | .highlight .ni { color: #800080; }
100 |
101 | .highlight .ne { color: #990000; font-weight: bold; }
102 |
103 | .highlight .nf { color: #990000; font-weight: bold; }
104 |
105 | .highlight .nl { color: #990000; font-weight: bold; }
106 |
107 | .highlight .nn { color: #555555; }
108 |
109 | .highlight .nt { color: #000080; }
110 |
111 | .highlight .vc { color: #008080; }
112 |
113 | .highlight .vg { color: #008080; }
114 |
115 | .highlight .vi { color: #008080; }
116 |
117 | .highlight .nv { color: #008080; }
118 |
119 | .highlight .ow { color: #000000; font-weight: bold; }
120 |
121 | .highlight .o { color: #000000; font-weight: bold; }
122 |
123 | .highlight .w { color: #bbbbbb; }
124 |
125 | .highlight { background-color: #f8f8f8; }
126 |
127 | * {
128 | box-sizing: border-box; }
129 |
130 | body {
131 | padding: 0;
132 | margin: 0;
133 | font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
134 | font-size: 16px;
135 | line-height: 1.5;
136 | color: #606c71; }
137 |
138 | a {
139 | color: #1e6bb8;
140 | text-decoration: none; }
141 | a:hover {
142 | text-decoration: underline; }
143 |
144 | .btn {
145 | display: inline-block;
146 | margin-bottom: 1rem;
147 | color: rgba(255, 255, 255, 0.7);
148 | background-color: rgba(255, 255, 255, 0.08);
149 | border-color: rgba(255, 255, 255, 0.2);
150 | border-style: solid;
151 | border-width: 1px;
152 | border-radius: 0.3rem;
153 | transition: color 0.2s, background-color 0.2s, border-color 0.2s; }
154 | .btn:hover {
155 | color: rgba(255, 255, 255, 0.8);
156 | text-decoration: none;
157 | background-color: rgba(255, 255, 255, 0.2);
158 | border-color: rgba(255, 255, 255, 0.3); }
159 | .btn + .btn {
160 | margin-left: 1rem; }
161 | @media screen and (min-width: 64em) {
162 | .btn {
163 | padding: 0.75rem 1rem; } }
164 | @media screen and (min-width: 42em) and (max-width: 64em) {
165 | .btn {
166 | padding: 0.6rem 0.9rem;
167 | font-size: 0.9rem; } }
168 | @media screen and (max-width: 42em) {
169 | .btn {
170 | display: block;
171 | width: 100%;
172 | padding: 0.75rem;
173 | font-size: 0.9rem; }
174 | .btn + .btn {
175 | margin-top: 1rem;
176 | margin-left: 0; } }
177 |
178 | .page-header {
179 | color: #fff;
180 | text-align: center;
181 | background-color: #159957;
182 | background-image: linear-gradient(120deg, #155799, #272727); }
183 | @media screen and (min-width: 64em) {
184 | .page-header {
185 | padding: 5rem 6rem; } }
186 | @media screen and (min-width: 42em) and (max-width: 64em) {
187 | .page-header {
188 | padding: 3rem 4rem; } }
189 | @media screen and (max-width: 42em) {
190 | .page-header {
191 | padding: 2rem 1rem; } }
192 |
193 | .project-name {
194 | margin-top: 0;
195 | margin-bottom: 0.1rem; }
196 | @media screen and (min-width: 64em) {
197 | .project-name {
198 | font-size: 3.25rem; } }
199 | @media screen and (min-width: 42em) and (max-width: 64em) {
200 | .project-name {
201 | font-size: 2.25rem; } }
202 | @media screen and (max-width: 42em) {
203 | .project-name {
204 | font-size: 1.75rem; } }
205 |
206 | .project-tagline {
207 | margin-bottom: 2rem;
208 | font-weight: normal;
209 | opacity: 0.7; }
210 | @media screen and (min-width: 64em) {
211 | .project-tagline {
212 | font-size: 1.25rem; } }
213 | @media screen and (min-width: 42em) and (max-width: 64em) {
214 | .project-tagline {
215 | font-size: 1.15rem; } }
216 | @media screen and (max-width: 42em) {
217 | .project-tagline {
218 | font-size: 1rem; } }
219 |
220 | .main-content {
221 | word-wrap: break-word; }
222 | .main-content :first-child {
223 | margin-top: 0; }
224 | @media screen and (min-width: 64em) {
225 | .main-content {
226 | max-width: 64rem;
227 | padding: 2rem 6rem;
228 | margin: 0 auto;
229 | font-size: 1.1rem; } }
230 | @media screen and (min-width: 42em) and (max-width: 64em) {
231 | .main-content {
232 | padding: 2rem 4rem;
233 | font-size: 1.1rem; } }
234 | @media screen and (max-width: 42em) {
235 | .main-content {
236 | padding: 2rem 1rem;
237 | font-size: 1rem; } }
238 | .main-content img {
239 | max-width: 100%; }
240 | .main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 {
241 | margin-top: 2rem;
242 | margin-bottom: 1rem;
243 | font-weight: normal;
244 | color: #155399; }
245 | .main-content p {
246 | margin-bottom: 1em; }
247 | .main-content code {
248 | padding: 2px 4px;
249 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
250 | font-size: 0.9rem;
251 | color: #383e41;
252 | background-color: #f3f6fa;
253 | border-radius: 0.3rem; }
254 | .main-content pre {
255 | padding: 0.8rem;
256 | margin-top: 0;
257 | margin-bottom: 1rem;
258 | font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace;
259 | color: #567482;
260 | word-wrap: normal;
261 | background-color: #f3f6fa;
262 | border: solid 1px #dce6f0;
263 | border-radius: 0.3rem; }
264 | .main-content pre > code {
265 | padding: 0;
266 | margin: 0;
267 | font-size: 0.9rem;
268 | color: #567482;
269 | word-break: normal;
270 | white-space: pre;
271 | background: transparent;
272 | border: 0; }
273 | .main-content .highlight {
274 | margin-bottom: 1rem; }
275 | .main-content .highlight pre {
276 | margin-bottom: 0;
277 | word-break: normal; }
278 | .main-content .highlight pre, .main-content pre {
279 | padding: 0.8rem;
280 | overflow: auto;
281 | font-size: 0.9rem;
282 | line-height: 1.45;
283 | border-radius: 0.3rem;
284 | -webkit-overflow-scrolling: touch; }
285 | .main-content pre code, .main-content pre tt {
286 | display: inline;
287 | max-width: initial;
288 | padding: 0;
289 | margin: 0;
290 | overflow: initial;
291 | line-height: inherit;
292 | word-wrap: normal;
293 | background-color: transparent;
294 | border: 0; }
295 | .main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after {
296 | content: normal; }
297 | .main-content ul, .main-content ol {
298 | margin-top: 0; }
299 | .main-content blockquote {
300 | padding: 0 1rem;
301 | margin-left: 0;
302 | color: #819198;
303 | border-left: 0.3rem solid #dce6f0; }
304 | .main-content blockquote > :first-child {
305 | margin-top: 0; }
306 | .main-content blockquote > :last-child {
307 | margin-bottom: 0; }
308 | .main-content table {
309 | display: block;
310 | width: 100%;
311 | overflow: auto;
312 | word-break: normal;
313 | word-break: keep-all;
314 | -webkit-overflow-scrolling: touch; }
315 | .main-content table th {
316 | font-weight: bold; }
317 | .main-content table th, .main-content table td {
318 | padding: 0.5rem 1rem;
319 | border: 1px solid #e9ebec; }
320 | .main-content dl {
321 | padding: 0; }
322 | .main-content dl dt {
323 | padding: 0;
324 | margin-top: 1rem;
325 | font-size: 1rem;
326 | font-weight: bold; }
327 | .main-content dl dd {
328 | padding: 0;
329 | margin-bottom: 1rem; }
330 | .main-content hr {
331 | height: 2px;
332 | padding: 0;
333 | margin: 1rem 0;
334 | background-color: #eff0f1;
335 | border: 0; }
336 |
337 | .site-footer {
338 | padding-top: 2rem;
339 | margin-top: 2rem;
340 | border-top: solid 1px #eff0f1; }
341 | @media screen and (min-width: 64em) {
342 | .site-footer {
343 | font-size: 1rem; } }
344 | @media screen and (min-width: 42em) and (max-width: 64em) {
345 | .site-footer {
346 | font-size: 1rem; } }
347 | @media screen and (max-width: 42em) {
348 | .site-footer {
349 | font-size: 0.9rem; } }
350 |
351 | .site-footer-owner {
352 | display: block;
353 | font-weight: bold; }
354 |
355 | .site-footer-credits {
356 | color: #819198; }
357 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/pedrofelix/kotlin/examples/spring-examples.kt:
--------------------------------------------------------------------------------
1 | package org.pedrofelix.kotlin.examples
2 |
3 | import kotlinx.coroutines.*
4 | import kotlinx.coroutines.future.await
5 | import org.slf4j.LoggerFactory
6 | import org.springframework.boot.autoconfigure.SpringBootApplication
7 | import org.springframework.boot.runApplication
8 | import org.springframework.web.bind.annotation.GetMapping
9 | import org.springframework.web.bind.annotation.RestController
10 | import org.springframework.web.context.request.async.AsyncRequestTimeoutException
11 | import org.springframework.web.context.request.async.DeferredResult
12 | import java.util.concurrent.Executors
13 | import kotlin.coroutines.ContinuationInterceptor
14 | import kotlin.coroutines.CoroutineContext
15 | import kotlin.coroutines.EmptyCoroutineContext
16 |
17 | private val log = LoggerFactory.getLogger(DemoApplication::class.java)
18 |
19 | // Just a very simple example distacher
20 | private val exampleDispatcher = Executors.newSingleThreadExecutor { r ->
21 | Thread(r).apply { name = "the-example-thread" }
22 | }.asCoroutineDispatcher()
23 |
24 | // The startup class
25 | @SpringBootApplication
26 | class DemoApplication
27 |
28 | fun main(args: Array) {
29 | runApplication(*args)
30 | }
31 |
32 | // The example Controller
33 | @RestController
34 | class TheController {
35 |
36 | // Simple example of using Coroutines on a Spring MVC controller
37 | // GlobalScope is not a good idea and will be handled in other handlers
38 | // However we need to start somewhere...
39 | // Using an explicit time to make it easy to demo
40 | @GetMapping("/example")
41 | fun getExample() = DeferredResult(5000).apply {
42 | log.info("controller handler starting")
43 | GlobalScope.launch {
44 | try {
45 | setResult(example7(1))
46 | } catch (ex: Throwable) {
47 | setErrorResult(ex)
48 | }
49 | }
50 | log.info("controller handler ending")
51 | }
52 |
53 | // Example using a small timeout so the problems of using GlobalScope
54 | // are illustrated
55 | @GetMapping("/example-with-small-timeout")
56 | fun getExampleWithSmallTimeout() = DeferredResult(1000).apply {
57 | log.info("controller handler starting")
58 | GlobalScope.launch {
59 | try {
60 | setResult(example7(1))
61 | } catch (ex: Throwable) {
62 | setErrorResult(ex)
63 | }
64 | }
65 | log.info("controller handler ending")
66 | }
67 |
68 | // And now using a proper scope to cancel the running coroutines on timeout
69 | @GetMapping("/example-with-small-timeout-and-scope")
70 | fun getExampleWithSmallTimeoutAndScope() = DeferredResultScope(1000).apply {
71 | log.info("controller handler starting")
72 | this.launch {
73 | try {
74 | setResult(example7(1))
75 | } catch (ex: Throwable) {
76 | log.info("catched '{}'", ex.message)
77 | setErrorResult(ex)
78 | }
79 | }
80 | log.info("controller handler ending")
81 | }
82 |
83 | // Using the asyncHandler function to make this pretty
84 | @GetMapping("/example-async-handler")
85 | fun getExampleWithAsyncHandler(input: Int) = asyncHandler {
86 | var acc = input
87 | for (i in 0..2) {
88 | log.info("before asyncOperationReturningFuture")
89 | val result = asyncOperationReturningFuture(acc).await()
90 | log.info("after asyncOperationReturningFuture")
91 | acc = doSomethingWith(result)
92 | }
93 | acc
94 | }
95 |
96 | // Using `withContext` to change the used dispatcher
97 | @GetMapping("/example-with-dispatcher")
98 | fun getExampleWithDispatcher() = asyncHandler {
99 | withContext(exampleDispatcher) {
100 | log.info("using interceptor {}", coroutineContext[ContinuationInterceptor])
101 | example7(1)
102 | }
103 | }
104 |
105 | // Passing the context directly on the helper `asyncHandler`
106 | @GetMapping("/example-with-dispatcher2")
107 | fun getExampleWithDispatcher2() = asyncHandler(context = exampleDispatcher) {
108 | example7(1)
109 | }
110 |
111 | @GetMapping("/example1")
112 | fun get1() = DeferredResult(5000).apply {
113 | log.info("starting controller handler")
114 | GlobalScope.launch {
115 | try {
116 | val s1 = oper1()
117 | val s2 = oper2()
118 | setResult("($s1) and ($s2)")
119 | } catch (ex: Throwable) {
120 | log.warn("catching '{}'", ex.message)
121 | setErrorResult(ex)
122 | }
123 | }
124 | log.info("returning from controller handler")
125 | }
126 |
127 | @GetMapping("/example2")
128 | fun get2() = DeferredResult(1000).apply {
129 | log.info("starting controller handler")
130 | GlobalScope.launch {
131 | try {
132 | val s1 = oper1()
133 | val s2 = oper2()
134 | setResult("($s1) and ($s2)")
135 | } catch (ex: Throwable) {
136 | log.warn("catching '{}'", ex.message)
137 | setErrorResult(ex)
138 | }
139 | }
140 | log.info("returning from controller handler")
141 | }
142 |
143 | @GetMapping("/example3")
144 | fun get3() = DeferredResultScope(1000).apply {
145 | log.info("starting controller handler")
146 | this.launch {
147 | try {
148 | val s1 = oper1()
149 | val s2 = oper2()
150 | setResult("($s1) and ($s2)")
151 | } catch (ex: Throwable) {
152 | log.warn("catching '{}'", ex.message)
153 | setErrorResult(ex)
154 | }
155 | }
156 | log.info("returning from controller handler")
157 | }
158 |
159 | @GetMapping("/example4")
160 | fun get4() = DeferredResultScope(5000).apply {
161 | log.info("starting controller handler")
162 | this.launch {
163 | try {
164 | val res = coroutineScope {
165 | val d1 = async { oper1() }
166 | val d2 = async { oper2() }
167 | "(${d1.await()}) and (${d2.await()})"
168 | }
169 | log.info("setting result $res")
170 | setResult(res)
171 | } catch (ex: Throwable) {
172 | log.warn("catching '{}'", ex.message)
173 | setErrorResult(ex)
174 | }
175 | }
176 | log.info("returning from controller handler")
177 | }
178 |
179 | @GetMapping("/example5")
180 | fun get5() = DeferredResultScope(1000).apply {
181 | log.info("starting controller handler")
182 | this.launch {
183 | try {
184 | val res = coroutineScope {
185 | val d1 = async { oper1() }
186 | val d2 = async { oper2() }
187 | "(${d1.await()}) and (${d2.await()})"
188 | }
189 | log.info("setting result $res")
190 | setResult(res)
191 | } catch (ex: Throwable) {
192 | log.warn("catching '{}'", ex.message)
193 | setErrorResult(ex)
194 | }
195 | }
196 | log.info("returning from controller handler")
197 | }
198 |
199 | @GetMapping("/example6")
200 | fun get6() = DeferredResultScope(5000).apply {
201 | log.info("starting controller handler")
202 | this.launch {
203 | try {
204 | val res = coroutineScope {
205 | val d1 = async { oper1() }
206 | val d2 = async { oper3() }
207 | "(${d1.await()}) and (${d2.await()})"
208 | }
209 | log.info("setting result $res")
210 | setResult(res)
211 | } catch (ex: Throwable) {
212 | log.warn("catching '{}'", ex.message)
213 | setErrorResult(ex)
214 | }
215 | }
216 | log.info("returning from controller handler")
217 | }
218 |
219 | @GetMapping("/example7")
220 | fun get7() = asyncHandler(5000) {
221 | val res = coroutineScope {
222 | val d1 = async { oper1() }
223 | val d2 = async { oper3() }
224 | "(${d1.await()}) and (${d2.await()})"
225 | }
226 | res
227 | }
228 |
229 | @GetMapping("/example8")
230 | fun get8() = asyncHandler(5000) {
231 | val res = coroutineScope {
232 | val d1 = async { oper1() }
233 | val d2 = async { oper2() }
234 | "(${d1.await()}) and (${d2.await()})"
235 | }
236 | res
237 | }
238 |
239 | @GetMapping("/example9")
240 | fun get9() = asyncHandler(1000) {
241 | val res = coroutineScope {
242 | val d1 = async { oper1() }
243 | val d2 = async { oper2() }
244 | "(${d1.await()}) and (${d2.await()})"
245 | }
246 | res
247 | }
248 |
249 | }
250 |
251 | // Helper functions below...
252 |
253 | private fun asyncHandler(
254 | ms: Long? = null,
255 | context: CoroutineContext = EmptyCoroutineContext,
256 | block: suspend CoroutineScope.() -> T) = DeferredResultScope(ms).apply {
257 | log.info("starting controller handler")
258 | this.launch(context) {
259 | try {
260 | val res = this.block()
261 | log.info("completing asyncHandler successfully")
262 | setResult(res)
263 | } catch (ex: Throwable) {
264 | log.info("completing asyncHandler with exception '{}'", ex.message)
265 | setErrorResult(ex)
266 | }
267 | }
268 | log.info("returning from controller handler")
269 | }
270 |
271 | class DeferredResultScope(private val timeout: Long? = null)
272 | : DeferredResult(timeout), CoroutineScope {
273 |
274 | private val job = Job()
275 | override val coroutineContext: CoroutineContext
276 | get() = job
277 |
278 | init {
279 | this.onTimeout {
280 | log.info("DeferredResult timed out")
281 | setErrorResult(AsyncRequestTimeoutException())
282 | job.cancel()
283 | }
284 | }
285 | }
286 |
287 | // Just for demo purposes ...
288 |
289 | private suspend fun oper1(): String {
290 | try {
291 | delay(2000)
292 | log.info("`oper1` ending")
293 | return "result from `oper1`"
294 | } catch (ex: CancellationException) {
295 | log.warn("`oper1` cancelled")
296 | throw ex
297 | }
298 | }
299 |
300 | private suspend fun oper2(): String {
301 | try {
302 | delay(2500)
303 | log.info("`oper2` ending")
304 | return "result from `oper2`"
305 | } catch (ex: CancellationException) {
306 | log.warn("`oper2` cancelled")
307 | throw ex
308 | }
309 | }
310 |
311 | private suspend fun oper3(): String {
312 | try {
313 | delay(500)
314 | log.warn("`oper3` ending with error")
315 | throw Exception("error on `oper3`")
316 | } catch (ex: CancellationException) {
317 | log.warn("`oper3` cancelled")
318 | throw ex
319 | }
320 | }
321 |
322 |
323 |
324 |
325 |
326 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/test/kotlin/org/pedrofelix/kotlin/examples/ScopeExamples.kt:
--------------------------------------------------------------------------------
1 | package org.pedrofelix.experiments
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Assert
5 | import org.junit.Assert.assertFalse
6 | import org.junit.Assert.assertTrue
7 | import org.junit.Test
8 | import org.slf4j.LoggerFactory
9 | import java.lang.AssertionError
10 | import java.util.concurrent.atomic.AtomicBoolean
11 | import java.util.concurrent.atomic.AtomicInteger
12 |
13 | private val log = LoggerFactory.getLogger(ScopeExamples::class.java)
14 |
15 | class ScopeExamples {
16 |
17 | @Test
18 | fun runBlocking_doesnt_throw_because_inner_coroutines_are_on_GlobalScope() {
19 | var called = AtomicBoolean()
20 | Thread.setDefaultUncaughtExceptionHandler { th, ex ->
21 | log.info("My exception handler: Exception {} on thread {}", ex.message, th.name)
22 | called.set(true)
23 | }
24 |
25 | val cr1 = Result()
26 | val cr2 = Result()
27 |
28 | log.info("start")
29 | runBlocking {
30 | GlobalScope.launch {
31 | throwAfter(500, cr1)
32 | }
33 | GlobalScope.launch {
34 | completeAfter(1000, cr2)
35 | }
36 | }
37 | // runBlocking doesn't wait on inner coroutines
38 | // (they are not connected because they were started in the GlobalScope)
39 | assertFalse(called.get())
40 | assertFalse(cr1.isCompleted())
41 | assertFalse(cr2.isCompleted())
42 | log.info("end")
43 |
44 | // waiting so the GlobalScope.launch have time to end
45 | Thread.sleep(1500);
46 | // however the default handler is called (look at the standard output)
47 | assertTrue(called.get())
48 | assertTrue(cr1.isError())
49 | assertTrue(cr2.isSuccess())
50 | }
51 |
52 | @Test
53 | fun runBlocking_doesnt_throw_and_there_is_not_default_handling_for_async() {
54 | var called = AtomicBoolean()
55 | Thread.setDefaultUncaughtExceptionHandler { th, ex ->
56 | log.info("My exception handler: Exception {} on thread {}", ex.message, th.name)
57 | called.set(true)
58 | }
59 | val cr1 = Result()
60 | val cr2 = Result()
61 | log.info("start")
62 | runBlocking {
63 | GlobalScope.async {
64 | throwAfter(500, cr1)
65 | }
66 | GlobalScope.async {
67 | completeAfter(1000, cr2)
68 | }
69 | }
70 | // runBlocking doesn't wait on inner coroutines
71 | // (they are not connected because they were started in the GlobalScope)
72 | assertFalse(cr1.isCompleted())
73 | assertFalse(cr2.isCompleted())
74 | log.info("end")
75 | Thread.sleep(1500);
76 | // and the default handler is not called
77 | assertFalse(called.get())
78 | assertTrue(cr1.isError())
79 | assertTrue(cr2.isSuccess())
80 | }
81 |
82 | @Test
83 | fun runBlocking_throws_due_to_child_coroutine_exception() {
84 | val cr1 = Result()
85 | val cr2 = Result()
86 | log.info("start")
87 | assertExceptionHappens {
88 | runBlocking {
89 | launch {
90 | throwAfter(500, cr1)
91 | }
92 | launch {
93 | completeAfter(1000, cr2)
94 | }
95 | log.info("after launches")
96 | }
97 | }
98 | assertTrue(cr1.isCompleted())
99 | assertTrue(cr2.isCompleted())
100 | assertTrue(cr1.isError())
101 | assertTrue(cr2.isCancelled())
102 | log.info("end")
103 | }
104 |
105 | @Test
106 | fun runBlocking_throws_due_to_child_coroutine_exception_async() {
107 | val cr1 = Result()
108 | val cr2 = Result()
109 | log.info("start")
110 | assertExceptionHappens {
111 | runBlocking {
112 | async {
113 | throwAfter(500, cr1)
114 | }
115 | async {
116 | completeAfter(1000, cr2)
117 | }
118 | log.info("after asyncs")
119 | }
120 | }
121 | assertTrue(cr1.isCompleted())
122 | assertTrue(cr2.isCompleted())
123 | assertTrue(cr1.isError())
124 | assertTrue(cr2.isCancelled())
125 | log.info("end")
126 | }
127 |
128 | @Test
129 | fun nested_coroutines() {
130 | var called = AtomicBoolean()
131 | Thread.setDefaultUncaughtExceptionHandler { th, ex ->
132 | log.info("My exception handler: Exception {} on thread {}", ex.message, th.name)
133 | called.set(true)
134 | }
135 | val cr1 = Result()
136 | val cr2 = Result()
137 | log.info("start")
138 | // No exception because the `GlobalScope` filters out the errors from the inner async
139 | runBlocking {
140 | GlobalScope.launch {
141 | async {
142 | throwAfter(500, cr1)
143 | }
144 | async {
145 | completeAfter(1000, cr2)
146 | }
147 | }
148 | }
149 | log.info("end")
150 | assertFalse(cr1.isCompleted())
151 | assertFalse(cr2.isCompleted())
152 | // waiting for background coroutines to end
153 | Thread.sleep(2000)
154 | assertTrue(called.get())
155 | assertTrue(cr1.isError())
156 | assertTrue(cr2.isCancelled())
157 | }
158 |
159 | @Test
160 | fun nested_coroutines2() {
161 | val called = AtomicBoolean()
162 | Thread.setDefaultUncaughtExceptionHandler { th, ex ->
163 | log.info("My exception handler: Exception {} on thread {}", ex.message, th.name)
164 | called.set(true)
165 | }
166 | val cr1 = Result()
167 | val cr2 = Result()
168 | log.info("start")
169 | runBlocking {
170 | val job = GlobalScope.launch {
171 | async {
172 | throwAfter(500, cr1)
173 | }
174 | async {
175 | completeAfter(1000, cr2)
176 | }
177 | }
178 | assertFalse(cr1.isCompleted())
179 | assertFalse(cr2.isCompleted())
180 | // This will not throw, which seems odd. TODO need to check if it's a bug or not
181 | job.join()
182 | assertTrue(cr1.isCompleted())
183 | assertTrue(cr2.isCompleted())
184 | log.info("after join")
185 | }
186 | // They are completed because the `join` waits for it
187 | assertTrue(cr1.isCompleted())
188 | assertTrue(cr2.isCompleted())
189 | log.info("end")
190 | Thread.sleep(2000)
191 | assertTrue(cr1.isError())
192 | assertTrue(cr2.isCancelled())
193 | }
194 |
195 | @Test
196 | fun nested_coroutines3() {
197 | val called = AtomicBoolean()
198 | val cr1 = Result()
199 | val cr2 = Result()
200 | log.info("start")
201 | assertExceptionHappens {
202 | runBlocking {
203 | val job = launch {
204 | async {
205 | throwAfter(500, cr1)
206 | }
207 | async {
208 | completeAfter(1000, cr2)
209 | }
210 | }
211 | assertFalse(cr1.isCompleted())
212 | assertFalse(cr2.isCompleted())
213 | job.join()
214 | log.info("after join")
215 | }
216 | }
217 | assertTrue(cr1.isCompleted())
218 | assertTrue(cr2.isCompleted())
219 | log.info("end")
220 | }
221 |
222 | @Test
223 | fun nested_coroutines4() {
224 | val cr1 = Result()
225 | val cr2 = Result()
226 | val cr3 = Result()
227 | log.info("start")
228 | val start = System.currentTimeMillis()
229 | fun took() = System.currentTimeMillis() - start
230 | val someError = 500
231 | val res = runBlocking {
232 | launch {
233 | completeAfter(2000, cr1)
234 | }
235 | val d1 = async {
236 | completeAfter(500, cr2)
237 | 1
238 | }
239 | val d2 = async {
240 | completeAfter(1000, cr3)
241 | 2
242 | }
243 | log.info("before await")
244 | assertFalse(cr1.isCompleted())
245 | assertFalse(cr2.isCompleted())
246 | assertFalse(cr3.isCompleted())
247 | val sum = d1.await() + d2.await()
248 | assertFalse(cr1.isCompleted())
249 | assertTrue(cr2.isCompleted())
250 | assertTrue(cr3.isCompleted())
251 | log.info("after await, before return")
252 | assertTrue(took() < 1000 + someError)
253 | sum
254 | }
255 | assertTrue(cr1.isCompleted())
256 | assertTrue(cr2.isCompleted())
257 | assertTrue(cr3.isCompleted())
258 | assertTrue(took() >= 2000)
259 | log.info("end")
260 | }
261 |
262 | @Test
263 | fun nested_coroutines5() {
264 | val crs = Array(5) { Result() }
265 | log.info("start")
266 | val start = System.currentTimeMillis()
267 | fun took() = System.currentTimeMillis() - start
268 | runBlocking {
269 | launch {
270 | launch {
271 | completeAfter(2500, crs[1])
272 | }
273 | completeAfter(2000, crs[0])
274 | }
275 | GlobalScope.launch {
276 | completeAfter(3000, crs[2])
277 | }
278 | val d1 = async {
279 | completeAfter(500, crs[3])
280 | 1
281 | }
282 | val d2 = async {
283 | completeAfter(1000, crs[4])
284 | 2
285 | }
286 | log.info("before await")
287 | assertFalse(crs.any { it.isCompleted() })
288 | val sum = d1.await() + d2.await()
289 | log.info("after await, before return")
290 | sum
291 | }
292 |
293 | // Notice how the outer runBlocking will wait for the
294 | // completeAfter(2500) which is two levels deep
295 | assertTrue(took() >= 2500)
296 | assertTrue(crs.all { it.isCompleted() })
297 | log.info("end")
298 | }
299 |
300 | fun assertExceptionHappens(block: () -> Unit): Unit {
301 | try {
302 | block()
303 | Assert.fail("must thrown an exception")
304 | } catch (ex: AssertionError) {
305 | throw ex
306 | } catch (ex: Throwable) {
307 | log.info("expected exception '{}'", ex.message)
308 | }
309 | }
310 |
311 | // Just for demo purposes ...
312 |
313 | class Result {
314 | private val state = AtomicInteger(0)
315 | fun complete() = state.set(1)
316 | fun completeWithCancellation() = state.set(2)
317 | fun completeWithError() = state.set(3)
318 | fun isCompleted() = state.get() != 0
319 | fun isSuccess() = state.get() == 1
320 | fun isCancelled() = state.get() == 2
321 | fun isError() = state.get() == 3
322 | }
323 |
324 | suspend fun throwAfter(ms: Long, res: Result) {
325 | try {
326 | delay(ms)
327 | } catch (ex: CancellationException) {
328 | log.warn("cancelled")
329 | res.completeWithCancellation()
330 | return
331 | }
332 | log.info("throwing")
333 | res.completeWithError()
334 | throw Exception("coroutine 2 ending with error")
335 | }
336 |
337 | suspend fun completeAfter(ms: Long, res: Result) {
338 | try {
339 | delay(ms)
340 | } catch (ex: CancellationException) {
341 | log.warn("cancelled")
342 | res.completeWithCancellation()
343 | return
344 | }
345 | log.info("completing")
346 | res.complete()
347 | }
348 |
349 | }
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # An introduction to Kotlin Coroutines #
2 |
3 | ## Motivation ##
4 |
5 | Asynchronous and concurrent programming plays an important role in the current world of Web APIs and microservices, where a significant part of our code is about orchestrating network interactions.
6 | Using traditional synchronous models, where threads are blocked while waiting for external responses, is not suitable for platforms where threads are costly, such as .NET or the JVM, or where there are special threads that can't be blocked, such as Javascript or Android applications.
7 |
8 | There are various programming models to handle asynchronicity, ranging from simple callbacks to [reactive streams](http://www.reactive-streams.org) as a way to handle asynchronous sequences.
9 | Among these, the concept of [*future*](https://en.wikipedia.org/wiki/Futures_and_promises) has seen broad adoption in multiple platforms (e.g. [Promises and thenables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in javascript, [`CompletableFuture`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) in Java 8, [`Task`](https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx) in .NET), including language support via the async-await constructs, which are now available in languages such as C#, Javascript, and Python.
10 | However, instead of also adding explicit async-await support in the Kotlin language, their designers decided to go a different route and addressed these problems using the different and more generic, although rather old, concept of coroutines.
11 |
12 | This document provides a slow-paced introduction to Kotlin coroutines, as implemented via suspending functions, and their use to write asynchronous and concurrent programs.
13 | Starting from the ground-up, we show how suspending functions allows us to turn callbacks into suspension points that don't break the apparent control flow.
14 | From then we move to creating and starting coroutines as instances of these suspending functions, taking a look into the underlying state machine and continuation interfaces.
15 | With this knowledge, we show how the async-await construct can be implemented as library functions without needing explicit language support.
16 | We also show how to achieve interoperability with other JVM asynchronous constructs, converting between them and coroutines.
17 |
18 | Kotlin coroutines are currently in experiment status, however their use is highly encouraged by the language designers and constitute a important mechanism for anyone programming connected systems.
19 |
20 | ## Coroutines and suspending functions ##
21 |
22 | Let's start our journey into coroutines with a simple plain function that takes some parameters, performs two steps and returns a value.
23 |
24 | ```kotlin
25 | fun simpleFunction(a: Int, b: Int): Int {
26 | log.info("step 1")
27 | log.info("step 2")
28 | return a + b
29 | }
30 | ```
31 |
32 | Calling this function from our `main` entry point function
33 |
34 | ```kotlin
35 | fun main(args: Array) {
36 | log.info("main started")
37 | log.info("result is {}", simpleFunction(40, 2))
38 | log.info("main ended")
39 | }
40 | ```
41 |
42 | produces the following log messages
43 |
44 | ```
45 | 8 [main] INFO intro - main started
46 | 9 [main] INFO intro - step 1
47 | 9 [main] INFO intro - step 2
48 | 10 [main] INFO intro - result is 42
49 | 10 [main] INFO intro - main ended
50 | ```
51 |
52 | The value between brackets contains the names for the thread where the `log.info` calls were performed: in this example all were performed on the `main` thread (the one that calls the `main` method).
53 | The following diagram illustrates the execution of `simpleFunction`, highlighting the fact that all statements are executed in same thread.
54 |
55 | 
56 |
57 | Now, suppose that between `step 1` and `step 2` we need to wait for something to happen, such as receiving a message from an external system or waiting for a time period to elapse.
58 | To keep things simple lets illustrate that using a `Thread.sleep`
59 |
60 | ```kotlin
61 | fun simpleFunctionWithDelay(a: Int, b: Int): Int {
62 | log.info("step 1")
63 | Thread.sleep(1000)
64 | log.info("step 2")
65 | return a + b
66 | }
67 | ```
68 |
69 | Running this function from our main method produces the following output
70 |
71 | ```
72 | 7 [main] INFO intro - main started
73 | 8 [main] INFO intro - step 1
74 | 1011 [main] INFO intro - step 2
75 | 1012 [main] INFO intro - result is 42
76 | 1012 [main] INFO intro - main ended
77 | ```
78 |
79 | Again, all the statements are run on the `main` thread, which blocks for approximately 1000 ms between `step 1` and `step 2`.
80 |
81 | 
82 |
83 | However, blocking threads may not be a good thing:
84 |
85 | * On client applications (e.g. Android applications), if the blocked thread is the GUI (Graphical User Interface) thread then the application will become unresponsive during the blocking period.
86 |
87 | * On server applications, blocking threads will reduce the number of threads available to process new incoming requests and therefore reduce the system's throughput.
88 |
89 | Kotlin **suspending** functions provide us with a way of handling these *pauses* in a sequential flow of statements without blocking the hosting thread.
90 | Namely, it allows a function
91 | * to suspend its execution, by returning immediately to its caller and "free up" the hosting thread.
92 | * resume its execution in a future point in time, potentially on a different thread.
93 |
94 | So, lets convert the previous `simpleFunctionWithDelay` example to a suspending function that does not block the hosting thread for the 1000 ms period, using suspending functions:
95 |
96 | ```kotlin
97 | suspend fun suspendFunctionWithDelay(a: Int, b: Int): Int {
98 | log.info("step 1")
99 | suspendCoroutine { cont ->
100 | executor.schedule(
101 | { cont.resume(Unit) },
102 | 1000,TimeUnit.MILLISECONDS)
103 | }
104 | log.info("step 2")
105 | return a + b
106 | }
107 | ```
108 |
109 | The first thing to notice is that a suspending function declaration is prefixed with the `suspend` keywords.
110 | However, the remaining function signature is unchanged: it still receives two integers and returns an integer.
111 | As a comparison, in C#, an `async` function that asynchronously returns a `int` will have `Task` as the return type and not `int`.
112 |
113 | Looking into the function body we notice that it remains mostly unchanged, except for the `Thread.sleep` call that was replaced by a call to `suspendCoroutine`.
114 |
115 | This `suspendCoroutine` function, available in the `kotlin.coroutines.experimental` package, is used to *suspend* the function when it is invoked.
116 | It has the following signature
117 |
118 | ```kotlin
119 | public inline suspend fun suspendCoroutine(crossinline block: (Continuation) -> Unit): T
120 | ```
121 |
122 | The main thing to understand is the `block` parameter, which is a function receiving a *continuation* representing the resume point after the suspension.
123 | A continuation is an object implementing the following interface
124 |
125 | ```kotlin
126 | public interface Continuation {
127 | /**
128 | * Resumes the execution of the corresponding coroutine passing [value] as the return value of the last suspension point.
129 | */
130 | public fun resume(value: T)
131 |
132 | /**
133 | * Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
134 | * last suspension point.
135 | */
136 | public fun resumeWithException(exception: Throwable)
137 |
138 | /**
139 | * Context of the coroutine that corresponds to this continuation.
140 | */
141 | public val context: CoroutineContext
142 | }
143 | ```
144 |
145 | Ignoring the `context` field for the moment being, a `Continuation` has two members:
146 | * the `resume` function, to be called if the suspending function should resume normally with a value;
147 | * and the `resumeWithException` function to be called if the suspending function should resume with an exception.
148 |
149 | In our case, the continuation will _sort of reference_ the statement `log.info("step 2")`, i.e., the statement after the point where the function called `suspendCoroutine`.
150 | It is the responsibility of the function invoking `suspendCoroutine` to pass a `block` that does something with that continuation.
151 |
152 | In our case, we just we use a plain Java `ScheduledExecutorService`
153 | ```kotlin
154 | val executor = Executors.newScheduledThreadPool(1)
155 | ```
156 |
157 | to schedule the execution of the continuation after 1000 ms.
158 | ```kotlin
159 | suspendCoroutine { cont ->
160 | executor.schedule(
161 | { cont.resume(Unit) },
162 | 1000,TimeUnit.MILLISECONDS)
163 | }
164 | ```
165 |
166 | The `{ cont.resume(Unit) }` is just the `Runnable` passed into the executor.
167 |
168 | When the `main` thread enters the `suspendCoroutine` function, the passed in block will be called with the continuation and a callback will be scheduled on the executor.
169 | After this, the `main` thread returns from the `suspendCoroutine` function and also from the `suspendCoroutine`.
170 | Yes, that is right, the `main` thread does not continue to the `log.info("step 2")`
171 |
172 | The following diagram illustrates the suspension and later resumption of the `suspendFunctionWithDelay`function.
173 |
174 | 
175 |
176 | The `main` thread enters the `suspendFunctionWithDelay`, executes the first step and then enters the `suspendCoroutine`, where the passed in `block` is immediately called, scheduling the `cont.resume(Unit)` to be run after 1000 ms.
177 | After this, the `main` thread leaves the `suspendCoroutine` and, since this function is marked as suspend, it also leaves the `suspendFunctionWithDelay`.
178 | That's right, the `main` thread does not continue to step 2.
179 | Instead the `suspendFunctionWithDelay` execution is suspended without blocking the `main` thread.
180 |
181 | After the 1000 ms elapse, a thread from the scheduled pool (in orange color) calls `cont.resume(Unit)`, resuming the execution of the `suspendFunctionWithDelay`.
182 | This suspension and resumption, including the switch between threads, is visible in the program output
183 |
184 | ```
185 | 8 [main] INFO intro - main started
186 | 20 [main] INFO intro - step 1
187 | 24 [main] INFO intro - main ended
188 | 1027 [pool-1-thread-1] INFO intro - step 2
189 | 1029 [pool-1-thread-1] INFO intro - result is 42
190 | ```
191 |
192 | Notice that the `main` function ends immediately after `step 1`, without waiting for the 1000 ms to elapse, because `suspendFunctionWithDelay` suspended its execution and *returns* to the `main` function.
193 | After the 1000 ms elapses, the `suspendFunctionWithDelay` resumes its execution in the `pool-1-thread-1` (a thread from the scheduled pool) and `step 2` is executed.
194 |
195 | Using `suspendCoroutine` directly in our `suspendFunctionWithDelay` makes the code a slightly brittle to read, namely due to the nested lambda passed as parameter.
196 | However, that can be easily handled by wrapping that behavior on an helper suspending function
197 |
198 | ```kotlin
199 | suspend fun delay(ms: Long) {
200 | suspendCoroutine { continuation ->
201 | executor.schedule({ continuation.resume(Unit) }, ms, TimeUnit.MILLISECONDS)
202 | }
203 | }
204 | ```
205 |
206 | The `suspendFunctionWithDelay` now becomes
207 | ```kotlin
208 | suspend fun suspendFunctionWithDelay2(a: Int, b: Int): Int {
209 | log.info("step 1")
210 | delay(1000)
211 | log.info("step 2")
212 | return a + b
213 | }
214 | ```
215 | which is as readable as our initial `simpleFunctionWithDelay` that used `Thread.sleep`, however has a non-blocking behavior.
216 | In fact, the only thing different in `suspendFunctionWithDelay` is the fact that it is has the `suspend` modifier.
217 | Every thing else is equal to the plain old `simpleFunctionWithDelay` blocking function, namely:
218 | * No change in the function signature; it still returns a plain `Int`.
219 | * No special keywords used in the function body.
220 | However the `simpleFunctionWithDelay` exhibits the remarking behavior of
221 | * being after the `delay` call, freeing up the calling thread;
222 | * resuming execution after 1000 ms on a *different* thread.
223 |
224 | However, we are still missing an important part of the picture
225 | Suspending functions can call regular functions or other suspending functions.
226 | For instance, in the previous example the `suspendFunctionWithDelay` is a suspending function so it can call `delay` directly, which is also a suspending function.
227 |
228 | However, suspending functions cannot be be called directly from regular functions.
229 | Namely, our regular `main` function cannot call `suspendFunctionWithDelay` directly.
230 | For that we need to use another function provided by the Kotlin library named `startCoroutine`, which is an regular (i.e. non-suspending) extension function over a `suspending lambda`.
231 |
232 | ```kotlin
233 | public fun (suspend () -> T).startCoroutine(
234 | completion: Continuation
235 | )
236 | ```
237 |
238 | The `startCoroutine` receives:
239 | * the suspending lambda to start (as the extension target).
240 | * the continuation to use when the suspending function completes its execution.
241 |
242 | Using it we can create a simple `startAndForget` function
243 | ```kotlin
244 | fun startAndForget(suspendingFunction: suspend () -> Unit) {
245 | suspendingFunction.startCoroutine(object : Continuation {
246 | override fun resume(value: Unit) {
247 | // forget it
248 | }
249 |
250 | override fun resumeWithException(exception: Throwable) {
251 | // forget it
252 | }
253 |
254 | override val context: CoroutineContext
255 | get() = EmptyCoroutineContext
256 | })
257 | }
258 | ```
259 | that starts a suspending function and ignores its result.
260 |
261 | We are now in condition to show the `main` function using the suspending version
262 | ```kotlin
263 | fun main(args: Array) {
264 | log.info("main started")
265 | startAndForget {
266 | log.info("result is {}", suspendFunctionWithDelay2(40, 2))
267 | }
268 | log.info("main ended")
269 | }
270 | ```
271 |
272 | which produces the output already shown before
273 |
274 | ```
275 | 8 [main] INFO intro - main started
276 | 20 [main] INFO intro - step 1
277 | 24 [main] INFO intro - main ended
278 | 1027 [pool-1-thread-1] INFO intro - step 2
279 | 1029 [pool-1-thread-1] INFO intro - result is 42
280 | ```
281 |
282 | Notice how the `result is 42` log message appears in the `pool-1-thread-1`, after the `main` function is terminated.
283 |
284 | The following diagram depicts the complete picture, including the `log.info` with the `suspendFunctionWithDelay2` returned value, as well as the final continuation.
285 | 
286 |
287 | The continuation passed in to startCoroutine allow us to do more interesting things than just ignoring the result.
288 | For instance, the following example uses a `CompletableFuture` to allow the `main` function to synchronize with the completable function termination.
289 |
290 | ```kotlin
291 | fun startAndGetFuture(suspendingFunction: suspend () -> Unit): CompletableFuture{
292 | val future = CompletableFuture()
293 | suspendingFunction.startCoroutine(object : Continuation {
294 | override fun resume(value: Unit) {
295 | future.complete(value)
296 | }
297 |
298 | override fun resumeWithException(exception: Throwable) {
299 | future.completeExceptionally(exception)
300 | }
301 |
302 | override val context: CoroutineContext
303 | get() = EmptyCoroutineContext
304 | })
305 | return future
306 | }
307 | ```
308 |
309 | With this, we can rewrite `main` to synchronize with the future completion.
310 | We use a blocking `future.get()` but on a real scenario we could also use a non-blocking `future.thenApply`.
311 |
312 | ```kotlin
313 | fun main(args: Array) {
314 | log.info("main started")
315 | val future = startAndGetFuture {
316 | log.info("result is {}", suspendFunctionWithDelay2(40, 2))
317 | }
318 | future.get()
319 | executor.shutdown()
320 | log.info("main ended")
321 | }
322 | ```
323 |
324 | Running this `main` function produced
325 |
326 | ```
327 | 8 [main] INFO intro - main started
328 | 16 [main] INFO intro - step 1
329 | 1022 [pool-1-thread-1] INFO intro - step 2
330 | 1024 [pool-1-thread-1] INFO intro - result is 42
331 | 1025 [main] INFO intro - main ended
332 | ```
333 |
334 | Notice how the `main` function only ends after the suspending function completely terminate (i.e. prints `result is 42`).
335 |
336 | Until now, and based solely on this simple example, all this suspending mechanics may seem a rather complex way to achieve something that could be done using a simple callback.
337 | However, the advantage of the coroutine mechanism starts to be apparent when the suspending functions is more than just an unconditional sequence of steps, such as the following example.
338 |
339 | ```kotlin
340 | suspend fun suspendFunctionWithDelayAndALoopWithConditionalLogic(a: Int, b: Int): Int {
341 | for(i in 0..3) {
342 | log.info("step 1 of iteration $i")
343 | if(i % 2 == 0) {
344 | delay(1000)
345 | }
346 | log.info("step 2 of iteration $i")
347 | }
348 | return a + b
349 | }
350 | ```
351 |
352 | In this case, the produced output is
353 |
354 | ```
355 | 8 [main] INFO intro - main started
356 | 17 [main] INFO intro - step 1 of iteration 0
357 | 1024 [pool-1-thread-1] INFO intro - step 2 of iteration 0
358 | 1024 [pool-1-thread-1] INFO intro - step 1 of iteration 1
359 | 1024 [pool-1-thread-1] INFO intro - step 2 of iteration 1
360 | 1024 [pool-1-thread-1] INFO intro - step 1 of iteration 2
361 | 2028 [pool-1-thread-1] INFO intro - step 2 of iteration 2
362 | 2028 [pool-1-thread-1] INFO intro - step 1 of iteration 3
363 | 2028 [pool-1-thread-1] INFO intro - step 2 of iteration 3
364 | 2031 [pool-1-thread-1] INFO intro - result is 42
365 | 2032 [main] INFO intro - main ended
366 | ```
367 |
368 |
--------------------------------------------------------------------------------