Skip to content

Commit 4913329

Browse files
committedJun 27, 2018
and now with files
1 parent 9450957 commit 4913329

File tree

19 files changed

+1276
-0
lines changed

19 files changed

+1276
-0
lines changed
 

‎build.gradle

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
group 'pmhsfelix.kotlin'
2+
version '1.0-SNAPSHOT'
3+
4+
buildscript {
5+
ext {
6+
kotlinVersion = '1.2.41'
7+
springBootVersion = '2.0.2.RELEASE'
8+
}
9+
repositories {
10+
mavenCentral()
11+
}
12+
dependencies {
13+
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
14+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
15+
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
16+
}
17+
}
18+
19+
apply plugin: 'kotlin'
20+
apply plugin: 'kotlin-spring'
21+
apply plugin: 'eclipse'
22+
apply plugin: 'org.springframework.boot'
23+
apply plugin: 'io.spring.dependency-management'
24+
25+
compileKotlin {
26+
kotlinOptions {
27+
freeCompilerArgs = ["-Xjsr305=strict"]
28+
jvmTarget = "1.8"
29+
}
30+
}
31+
compileTestKotlin {
32+
kotlinOptions {
33+
freeCompilerArgs = ["-Xjsr305=strict"]
34+
jvmTarget = "1.8"
35+
}
36+
}
37+
38+
kotlin {
39+
experimental {
40+
coroutines 'enable'
41+
}
42+
}
43+
44+
repositories {
45+
mavenCentral()
46+
jcenter()
47+
}
48+
49+
dependencies {
50+
compile "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${kotlinVersion}"
51+
compile('org.springframework.boot:spring-boot-starter-web')
52+
compile('com.fasterxml.jackson.module:jackson-module-kotlin')
53+
compile("org.jetbrains.kotlin:kotlin-reflect")
54+
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '0.22.5'
55+
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-jdk8', version: '0.22.5'
56+
compile group: 'org.asynchttpclient', name: 'async-http-client', version: '2.4.9'
57+
compile group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.3'
58+
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
59+
// runtime group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.25'
60+
testCompile 'junit:junit:4.12'
61+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
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

‎gradlew

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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" "$@"

‎gradlew.bat

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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

‎settings.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rootProject.name = 'coroutines'
2+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright (c) 2004-2011 QOS.ch
3+
* All rights reserved.
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining
6+
* a copy of this software and associated documentation files (the
7+
* "Software"), to deal in the Software without restriction, including
8+
* without limitation the rights to use, copy, modify, merge, publish,
9+
* distribute, sublicense, and/or sell copies of the Software, and to
10+
* permit persons to whom the Software is furnished to do so, subject to
11+
* the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be
14+
* included in all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
*
24+
*/
25+
package org.slf4j.impl;
26+
27+
import org.slf4j.helpers.BasicMDCAdapter;
28+
import org.slf4j.spi.MDCAdapter;
29+
30+
/**
31+
* This implementation is bound to {@link BasicMDCAdapter}.
32+
*
33+
* @author Ceki Gülcü
34+
*/
35+
@SuppressWarnings("unused")
36+
public class StaticMDCBinder {
37+
38+
/**
39+
* The unique instance of this class.
40+
*/
41+
public static final StaticMDCBinder SINGLETON = new StaticMDCBinder();
42+
43+
private StaticMDCBinder() {
44+
}
45+
46+
/**
47+
* Currently this method always returns an instance of
48+
* {@link BasicMDCAdapter}.
49+
*
50+
* @return MDCA Adapter
51+
*/
52+
public MDCAdapter getMDCA() {
53+
// note that this method is invoked only from within the static initializer of
54+
// the org.slf4j.MDC class.
55+
return new BasicMDCAdapter();
56+
}
57+
58+
public String getMDCAdapterClassStr() {
59+
return BasicMDCAdapter.class.getName();
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package pmhsfelix.kotlin.coroutines;
2+
3+
import com.fasterxml.jackson.databind.DeserializationFeature
4+
import com.fasterxml.jackson.databind.ObjectMapper
5+
import com.fasterxml.jackson.module.kotlin.readValue
6+
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
7+
import kotlinx.coroutines.experimental.*
8+
import kotlinx.coroutines.experimental.future.await
9+
import kotlinx.coroutines.experimental.future.future
10+
import org.apache.http.HttpResponse
11+
import org.apache.http.client.methods.HttpGet
12+
import org.apache.http.concurrent.FutureCallback
13+
import org.asynchttpclient.DefaultAsyncHttpClient
14+
import org.asynchttpclient.Response
15+
import org.slf4j.LoggerFactory
16+
import org.springframework.boot.autoconfigure.SpringBootApplication
17+
import org.springframework.boot.runApplication
18+
import org.springframework.context.annotation.Bean
19+
import org.springframework.web.bind.annotation.GetMapping
20+
import org.springframework.web.bind.annotation.RequestMapping
21+
import org.springframework.web.bind.annotation.RestController
22+
import org.springframework.web.servlet.HandlerInterceptor
23+
import org.springframework.web.servlet.ModelAndView
24+
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
25+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
26+
import java.util.concurrent.CompletableFuture
27+
import java.util.concurrent.atomic.AtomicInteger
28+
import javax.servlet.http.HttpServletRequest
29+
import javax.servlet.http.HttpServletResponse
30+
import org.apache.http.impl.nio.client.HttpAsyncClients
31+
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient
32+
import org.springframework.web.context.request.async.DeferredResult
33+
import java.lang.Exception
34+
import java.util.concurrent.TimeoutException
35+
import kotlin.coroutines.experimental.*
36+
37+
38+
@SpringBootApplication
39+
class SpringApp {
40+
@Bean
41+
fun config() = object : WebMvcConfigurer {
42+
override fun addInterceptors(registry: InterceptorRegistry) {
43+
registry.addInterceptor(object : HandlerInterceptor {
44+
override fun preHandle(request: HttpServletRequest,
45+
response: HttpServletResponse,
46+
handler: Any): Boolean {
47+
val start = System.nanoTime()
48+
request.setAttribute("ts", start)
49+
return true
50+
}
51+
52+
override fun postHandle(request: HttpServletRequest,
53+
response: HttpServletResponse,
54+
handler: Any, modelAndView: ModelAndView?) {
55+
val end = System.nanoTime()
56+
var start = request.getAttribute("ts") as Long
57+
log.info("Request to ${request.requestURI} took ${(end - start) / 1_000_000F} ms")
58+
}
59+
})
60+
}
61+
}
62+
}
63+
64+
fun main(args: Array<String>) {
65+
runApplication<SpringApp>(*args)
66+
}
67+
68+
private val log = LoggerFactory.getLogger(ExampleController::class.java)
69+
70+
@RestController
71+
@RequestMapping("/example")
72+
class ExampleController {
73+
74+
val client = DefaultAsyncHttpClient()
75+
val mapper = ObjectMapper()
76+
.registerKotlinModule()
77+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
78+
79+
fun get(url: String): CompletableFuture<Response> = client.prepareGet(url).execute().toCompletableFuture()
80+
fun getGitHub(url: String) = client.prepareGet(url)
81+
.addHeader("Authorization", "Bearer 1088a73d4016f27cbc606400f351849c3de197eb")
82+
.execute()
83+
.toCompletableFuture()
84+
85+
val apacheClient = HttpAsyncClients.createDefault().apply { start() }
86+
suspend fun apacheGetGitHub(url: String): HttpResponse {
87+
val get = HttpGet(url)
88+
get.addHeader("Authorization", "Bearer 1088a73d4016f27cbc606400f351849c3de197eb")
89+
return suspendCoroutine { cont ->
90+
apacheClient.execute(get, object : FutureCallback<HttpResponse> {
91+
override fun cancelled() {
92+
cont.resumeWithException(TimeoutException())
93+
}
94+
95+
override fun completed(result: HttpResponse?) {
96+
cont.resume(result!!)
97+
}
98+
99+
override fun failed(ex: Exception?) {
100+
cont.resumeWithException(ex!!)
101+
}
102+
})
103+
}
104+
}
105+
106+
107+
val counter = AtomicInteger()
108+
fun getId() = counter.getAndIncrement()
109+
110+
@GetMapping("/1")
111+
fun get1(): CompletableFuture<String?> {
112+
val cf = CompletableFuture<String?>()
113+
get("http://httpbin.org/get")
114+
.thenAccept { resp ->
115+
val body = resp.responseBody
116+
client.close()
117+
cf.complete(body)
118+
}
119+
return cf
120+
}
121+
122+
@GetMapping("/2")
123+
fun get2(): CompletableFuture<String?> =
124+
future {
125+
val resp = get("http://httpbin.org/get").await()
126+
resp.responseBody
127+
}
128+
129+
@GetMapping("/3")
130+
fun get3(): CompletableFuture<List<RepoAndTagsModel>> =
131+
future {
132+
val id = getId()
133+
log.info("[$id] Getting repo list")
134+
val resp = getGitHub("https://api.github.com/users/ktorio/repos").await()
135+
val repoList = mapper.readValue<List<RepoModel>>(resp.responseBody)
136+
repoList.map {
137+
log.info("[$id] Getting ${it.tags_url}")
138+
val resp = getGitHub(it.tags_url).await()
139+
val tags = mapper.readValue<List<TagModel>>(resp.responseBody)
140+
RepoAndTagsModel(it.name, tags.map { it.name })
141+
}
142+
}
143+
144+
@GetMapping("/4")
145+
fun get4(): CompletableFuture<List<RepoAndTagsModel>> =
146+
future(Unconfined) {
147+
val id = getId()
148+
log.info("[$id] Getting repo list")
149+
val resp = getGitHub("https://api.github.com/users/ktorio/repos").await()
150+
val repoList = mapper.readValue<List<RepoModel>>(resp.responseBody)
151+
log.info("[$id] Getting tags for all repos")
152+
val reqs = repoList.map { TagAsyncResponse(it.name, getGitHub(it.tags_url)) }
153+
reqs.map {
154+
log.info("[$id] Waiting for response")
155+
val resp = it.response.await()
156+
val tags = mapper.readValue<List<TagModel>>(resp.responseBody)
157+
RepoAndTagsModel(it.repoName, tags.map { it.name })
158+
}
159+
}
160+
161+
162+
@GetMapping("/5")
163+
fun get5(): CompletableFuture<List<RepoAndTagsModel>> =
164+
future(Unconfined) {
165+
val id = getId()
166+
log.info("[$id] Getting repo list")
167+
val resp = apacheGetGitHub("https://api.github.com/users/ktorio/repos")
168+
val repoList = mapper.readValue<List<RepoModel>>(resp.entity.content)
169+
log.info("[$id] Getting tags for all repos")
170+
val reqs = repoList.map { TagAsyncResponse(it.name, future(Unconfined) {apacheGetGitHub(it.tags_url)}) }
171+
reqs.map {
172+
log.info("[$id] Waiting for response")
173+
val resp = it.response.await()
174+
val tags = mapper.readValue<List<TagModel>>(resp.entity.content)
175+
RepoAndTagsModel(it.repoName, tags.map { it.name })
176+
}
177+
}
178+
179+
fun <T> deferred(block: suspend () -> T): DeferredResult<T> {
180+
val result = DeferredResult<T>()
181+
block.startCoroutine(object : Continuation<T> {
182+
override fun resume(value: T) {
183+
result.setResult(value)
184+
}
185+
186+
override fun resumeWithException(exception: Throwable) {
187+
result.setErrorResult(exception)
188+
}
189+
190+
override val context: CoroutineContext
191+
get() = EmptyCoroutineContext
192+
})
193+
return result
194+
}
195+
196+
@GetMapping("/tags")
197+
fun get6(): DeferredResult<List<RepoAndTags>> =
198+
deferred {
199+
getTagsForReposInOrgs("https://api.github.com/users/ktorio/repos")
200+
}
201+
}
202+
203+
204+
private data class TagAsyncResponse<T>(val repoName: String, val response: CompletableFuture<T>)
205+
private data class RepoModel(val name: String, val url: String, val tags_url: String)
206+
private data class TagModel(val name: String)
207+
data class RepoAndTagsModel(val name: String, val tags: List<String>)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import org.apache.http.HttpResponse
4+
import org.apache.http.client.methods.HttpGet
5+
import org.apache.http.client.methods.HttpUriRequest
6+
import org.apache.http.concurrent.FutureCallback
7+
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient
8+
import org.apache.http.nio.client.HttpAsyncClient
9+
import java.lang.Exception
10+
import java.util.concurrent.TimeoutException
11+
import kotlin.coroutines.experimental.suspendCoroutine
12+
13+
suspend fun HttpAsyncClient.execute(request: HttpUriRequest): HttpResponse =
14+
suspendCoroutine {continuation ->
15+
this.execute(request, object : FutureCallback<HttpResponse> {
16+
override fun cancelled() {
17+
continuation.resumeWithException(TimeoutException())
18+
}
19+
20+
override fun completed(result: HttpResponse?) {
21+
continuation.resume(result!!)
22+
}
23+
24+
override fun failed(ex: Exception?) {
25+
continuation.resumeWithException(ex!!)
26+
}
27+
})
28+
}
29+
30+
suspend fun HttpAsyncClient.get(url: String, token: String) =
31+
this.execute(HttpGet(url).apply {
32+
addHeader("Authorization", "Bearer $token")
33+
})
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import kotlinx.coroutines.experimental.future.await
4+
import kotlinx.coroutines.experimental.future.future
5+
import org.slf4j.LoggerFactory
6+
import java.util.concurrent.CompletableFuture
7+
import java.util.concurrent.TimeUnit
8+
9+
private val log = LoggerFactory.getLogger("intro")
10+
11+
fun <T> asyncDelay(ms: Long, value: T): CompletableFuture<T> {
12+
val future = CompletableFuture<T>()
13+
executor.schedule({
14+
future.complete(value)}
15+
, ms, TimeUnit.MILLISECONDS)
16+
return future
17+
}
18+
19+
fun main(args : Array<String>) {
20+
log.info("main started")
21+
//val f = future(Unconfined) {
22+
// val v1 = asyncDelay(1000, 1).await()
23+
// log.info("v1 is $v1")
24+
// val v2 = asyncDelay(1000, v1 + 1).await()
25+
// log.info("v2 is $v2")
26+
// v1 + v2
27+
//}
28+
val f = future {
29+
val f1 = asyncDelay(1000, 1)
30+
val f2 = asyncDelay(1000, 1)
31+
f1.await() + f2.await()
32+
}
33+
34+
log.info("before call to f.get()");
35+
log.info("final result is {}", f.get())
36+
executor.shutdown()
37+
log.info("main ended")
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import kotlinx.coroutines.experimental.CommonPool
4+
import kotlinx.coroutines.experimental.future.future
5+
import kotlinx.coroutines.experimental.launch
6+
import kotlinx.coroutines.experimental.runBlocking
7+
import org.slf4j.LoggerFactory
8+
import java.util.concurrent.TimeUnit
9+
import kotlin.coroutines.experimental.*
10+
11+
private val log = LoggerFactory.getLogger("intro")
12+
13+
fun decorateContext(ctx: CoroutineContext): CoroutineContext =
14+
ctx + decorateInterceptor(ctx[ContinuationInterceptor.Key])
15+
16+
fun decorateInterceptor(interceptor: ContinuationInterceptor?): ContinuationInterceptor =
17+
object : ContinuationInterceptor, AbstractCoroutineContextElement(ContinuationInterceptor) {
18+
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
19+
log("interceptContinuation ${continuation.hashCode()}") {
20+
interceptor?.interceptContinuation(loggingContinuation(continuation))
21+
?: loggingContinuation(continuation)
22+
}
23+
24+
}
25+
26+
fun <T> loggingContinuation(continuation: Continuation<T>) = object : Continuation<T> by continuation {
27+
override fun resume(value: T) {
28+
log("resume") {
29+
continuation.resume(value)
30+
}
31+
}
32+
33+
override fun resumeWithException(exception: Throwable) {
34+
35+
}
36+
}
37+
38+
fun <T> log(msg: String, block: ()->T) : T {
39+
log.info("before $msg")
40+
try {
41+
return block()
42+
}finally{
43+
log.info("after $msg")
44+
}
45+
}
46+
47+
48+
suspend fun delayWithLog(ms: Long) {
49+
suspendCoroutine<Unit> { continuation ->
50+
log.info("scheduling continuation ${continuation.hashCode()}")
51+
executor.schedule({ continuation.resume(Unit) }, ms, TimeUnit.MILLISECONDS)
52+
}
53+
log.info("delay: after suspendCoroutine")
54+
}
55+
56+
fun main(args: Array<String>) = runBlocking {
57+
58+
val job = launch (decorateContext(CommonPool)) {
59+
log.info("step 1")
60+
delayWithLog(1000)
61+
log.info("step 2")
62+
delayWithLog(1000)
63+
log.info("step 3")
64+
}
65+
job.join()
66+
67+
}
68+
69+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import kotlinx.coroutines.experimental.Unconfined
4+
import java.util.concurrent.CompletableFuture
5+
import kotlin.coroutines.experimental.Continuation
6+
import kotlin.coroutines.experimental.CoroutineContext
7+
import kotlin.coroutines.experimental.startCoroutine
8+
import kotlin.coroutines.experimental.suspendCoroutine
9+
10+
class A {}
11+
class B {}
12+
class C {}
13+
class D {}
14+
15+
fun g(a : A) : B = B()
16+
fun h(b : B) : C = C()
17+
fun l(c : C) : D = D()
18+
suspend fun hs(b : B) : C = C()
19+
fun hf(b : B) : CompletableFuture<C> {
20+
val cf = CompletableFuture<C>()
21+
cf.complete(C())
22+
return cf
23+
}
24+
25+
val x : (A, (B) -> Unit) -> B? = {_,_ -> B()}
26+
27+
fun f(a : A) : D {
28+
val x = g(a)
29+
val y = h(x)
30+
return l(y)
31+
}
32+
33+
suspend fun fs(a : A) : D {
34+
val x = g(a)
35+
val y = h(x)
36+
return l(y)
37+
}
38+
39+
suspend fun fs2(a : A) : D {
40+
val x = g(a)
41+
val y = hs(x)
42+
return l(y)
43+
}
44+
45+
suspend fun fs3(a : A) : D {
46+
val x = g(a)
47+
val fy = hf(x)
48+
val y = suspendCoroutine<C> { cont -> fy.thenApply({v -> cont.resume(v)}) }
49+
return l(y)
50+
}
51+
52+
fun start (block: suspend (A)->D, a : A ) : D {
53+
val cf = CompletableFuture<D>()
54+
block.startCoroutine(a, object : Continuation<D> {
55+
override fun resume(value: D) {
56+
cf.complete(value)
57+
}
58+
59+
override fun resumeWithException(exception: Throwable) {
60+
cf.completeExceptionally(exception)
61+
}
62+
63+
override val context: CoroutineContext
64+
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
65+
66+
})
67+
return cf.get()
68+
}
69+
70+
fun <T> createSequence(block: suspend MySequence<T>.() -> Unit) : MySequence<T> {
71+
val seq = MySequence<T>()
72+
val ctx = Unconfined
73+
block.startCoroutine<MySequence<T>, Unit>(seq, object : Continuation<Unit> {
74+
override val context: CoroutineContext
75+
get() = ctx
76+
77+
override fun resume(value: Unit) {
78+
seq.stop()
79+
}
80+
override fun resumeWithException(exception: Throwable) {
81+
seq.stop()
82+
}
83+
})
84+
return seq
85+
}
86+
87+
class MySequence<T> : Sequence<T>, Iterator<T> {
88+
var c : Continuation<Unit>? = null
89+
var value : T? = null
90+
var hasValue = false
91+
var ended = false
92+
93+
suspend fun yield(value : T) {
94+
println("yield $value")
95+
this.value = value
96+
this.hasValue = true
97+
suspendCoroutine<Unit> { cont ->
98+
c = cont
99+
}
100+
}
101+
102+
fun stop() { ended = true }
103+
104+
override fun next(): T {
105+
hasValue = false
106+
return value!!
107+
}
108+
109+
override fun hasNext(): Boolean {
110+
if(!hasValue) {
111+
c!!.resume(Unit)
112+
}
113+
return !ended
114+
}
115+
116+
override fun iterator(): Iterator<T> = this
117+
118+
}
119+
120+
fun main(args : Array<String>) {
121+
val seq = createSequence {
122+
yield(1)
123+
yield(2)
124+
yield(3)
125+
}
126+
println("before for")
127+
for(v in seq ){
128+
println(v)
129+
}
130+
}
131+
132+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import kotlinx.coroutines.experimental.Unconfined
4+
import java.util.concurrent.ScheduledThreadPoolExecutor
5+
import java.util.concurrent.TimeUnit
6+
import kotlin.coroutines.experimental.Continuation
7+
import kotlin.coroutines.experimental.CoroutineContext
8+
import kotlin.coroutines.experimental.startCoroutine
9+
import kotlin.coroutines.experimental.suspendCoroutine
10+
11+
//private val executor = ScheduledThreadPoolExecutor(1)
12+
13+
fun show(s : String) : Unit = println("${Thread.currentThread().name}, ${System.currentTimeMillis()}: $s")
14+
15+
fun aFunction () : String {
16+
show("first step")
17+
show("second step")
18+
return "done"
19+
}
20+
21+
fun aFunction2 () : String {
22+
show("first step")
23+
Thread.sleep(1000)
24+
show("second step")
25+
return "done"
26+
}
27+
28+
suspend fun aSuspendableFunction() : String {
29+
show("first step")
30+
suspendCoroutine<Unit> { cont ->
31+
executor.schedule<Unit>({ cont.resume(Unit) }, 1000, TimeUnit.MILLISECONDS)
32+
}
33+
show("second step")
34+
return "done"
35+
}
36+
37+
38+
39+
fun main(args : Array<String>) {
40+
//show(aFunction2())
41+
start { aSuspendableFunction() }
42+
43+
}
44+
45+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import java.util.concurrent.CompletableFuture
4+
import kotlin.coroutines.experimental.suspendCoroutine
5+
6+
suspend fun <T> CompletableFuture<T>.await(): T = suspendCoroutine<T> { cont ->
7+
this.whenComplete({value, error ->
8+
if (error != null)
9+
cont.resumeWithException(error)
10+
else
11+
cont.resume(value)
12+
})}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import kotlinx.coroutines.experimental.Unconfined
4+
import kotlinx.coroutines.experimental.runBlocking
5+
import org.slf4j.LoggerFactory
6+
import java.util.concurrent.CompletableFuture
7+
import java.util.concurrent.Executors
8+
import java.util.concurrent.TimeUnit
9+
import kotlin.coroutines.experimental.*
10+
11+
private val log = LoggerFactory.getLogger("intro")
12+
13+
fun simpleFunction(a: Int, b: Int): Int {
14+
log.info("step 1")
15+
log.info("step 2")
16+
return a + b
17+
}
18+
19+
fun simpleFunctionWithABlockingWait(a: Int, b: Int): Int {
20+
log.info("step 1")
21+
Thread.sleep(1000);
22+
log.info("step 2")
23+
return a + b
24+
}
25+
26+
val executor = Executors.newScheduledThreadPool(4)
27+
suspend fun suspendFunctionNonBlockingWait(a: Int, b: Int): Int {
28+
log.info("step 1")
29+
suspendCoroutine<Unit> { cont ->
30+
executor.schedule({ cont.resume(Unit) }, 1000, TimeUnit.MILLISECONDS)
31+
}
32+
log.info("step 2")
33+
return a + b
34+
}
35+
36+
suspend fun delay(ms: Long) {
37+
suspendCoroutine<Unit> { continuation ->
38+
executor.schedule({ continuation.resume(Unit) }, ms, TimeUnit.MILLISECONDS)
39+
}
40+
}
41+
suspend fun suspendFunctionNonBlockingWait2(a: Int, b: Int): Int {
42+
log.info("step 1")
43+
delay(1000)
44+
log.info("step 2")
45+
return a + b
46+
}
47+
48+
suspend fun suspendFunctionWithDelayAndALoopWithConditionalLogic(a: Int, b: Int): Int {
49+
for(i in 0..3) {
50+
log.info("step 1 of iteration $i")
51+
if(i % 2 == 0) {
52+
delay(1000)
53+
}
54+
log.info("step 2 of iteration $i")
55+
}
56+
return a + b
57+
}
58+
59+
fun <T> start(f: suspend () -> T) {
60+
f.startCoroutine(object : Continuation<T> {
61+
override val context: CoroutineContext
62+
get() = Unconfined
63+
64+
override fun resume(value: T) {
65+
log.info("result is {}", value)
66+
}
67+
68+
override fun resumeWithException(exception: Throwable) {
69+
log.error("ended with exception", exception)
70+
}
71+
})
72+
}
73+
74+
fun startAndForget(suspendableFunction: suspend () -> Unit) {
75+
suspendableFunction.startCoroutine(object : Continuation<Unit> {
76+
override fun resume(value: Unit) {
77+
// forget it
78+
}
79+
80+
override fun resumeWithException(exception: Throwable) {
81+
// forget it
82+
}
83+
84+
override val context: CoroutineContext
85+
get() = EmptyCoroutineContext
86+
})
87+
}
88+
89+
fun startAndGetFuture(suspendableFunction: suspend () -> Unit): CompletableFuture<Unit>{
90+
val future = CompletableFuture<Unit>()
91+
suspendableFunction.startCoroutine(object : Continuation<Unit> {
92+
override fun resume(value: Unit) {
93+
future.complete(value)
94+
}
95+
96+
override fun resumeWithException(exception: Throwable) {
97+
future.completeExceptionally(exception)
98+
}
99+
100+
override val context: CoroutineContext
101+
get() = EmptyCoroutineContext
102+
})
103+
return future
104+
}
105+
106+
fun main2(args: Array<String>) {
107+
log.info("main started")
108+
//log.info("result is {}", simpleFunction(40, 2))
109+
//log.info("result is {}", simpleFunctionWithDelay(40, 2))
110+
//startAndForget {
111+
// log.info("result is {}", suspendFunctionWithDelay2(40, 2))
112+
//}
113+
val future = startAndGetFuture {
114+
log.info("result is {}", suspendFunctionWithDelayAndALoopWithConditionalLogic(40, 2))
115+
}
116+
//val future = startFuture {
117+
// repeat(4) {
118+
// log.info("result is {}", suspendFunctionWithDelay2(40, 2))
119+
// }
120+
//}
121+
future.get()
122+
123+
executor.shutdown()
124+
log.info("main ended")
125+
}
126+
127+
fun main3(args: Array<String>) {
128+
log.info("main started")
129+
startAndForget {
130+
log.info("result is {}", suspendFunctionWithDelayAndALoopWithConditionalLogic(40, 2))
131+
}
132+
executor.schedule({}, 1000, TimeUnit.MILLISECONDS)
133+
executor.schedule({}, 2000, TimeUnit.MILLISECONDS)
134+
executor.schedule({}, 3000, TimeUnit.MILLISECONDS)
135+
//executor.shutdown()
136+
log.info("main ended")
137+
}
138+
139+
fun main(args: Array<String>) {
140+
log.info("main started")
141+
runBlocking(Unconfined) {
142+
//showTagsForReposInOrgs("https://api.github.com/users/ktorio/repos")
143+
showTagsForReposInOrgsUsingAsyncHttpClient("https://api.github.com/users/ktorio/repos")
144+
}
145+
log.info("main ended")
146+
}
147+
148+
149+
150+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import com.fasterxml.jackson.databind.DeserializationFeature
4+
import com.fasterxml.jackson.databind.ObjectMapper
5+
import com.fasterxml.jackson.module.kotlin.readValue
6+
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
7+
import org.apache.http.impl.nio.client.HttpAsyncClients
8+
import org.asynchttpclient.BoundRequestBuilder
9+
import org.asynchttpclient.DefaultAsyncHttpClient
10+
import org.slf4j.LoggerFactory
11+
12+
private val log = LoggerFactory.getLogger("repos")
13+
private val mapper = ObjectMapper()
14+
.registerKotlinModule()
15+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
16+
private val token = "1088a73d4016f27cbc606400f351849c3de197eb"
17+
fun BoundRequestBuilder.withToken() = this.addHeader("Authorization", "Token $token")
18+
19+
20+
suspend fun showTagsForReposInOrgs(orgUrl: String) {
21+
HttpAsyncClients.createDefault().use { client ->
22+
client.start()
23+
log.info("Getting repo list from $orgUrl")
24+
val resp = client.get(orgUrl, token)
25+
val repoList = mapper.readValue<List<Repo>>(resp.entity.content)
26+
repoList.map {
27+
log.info("Getting tags from ${it.tags_url}")
28+
val resp = client.get(it.tags_url, token)
29+
mapper.readValue<List<Tag>>(resp.entity.content).forEach {
30+
log.info("tag ${it.name} found")
31+
}
32+
}
33+
}
34+
}
35+
36+
suspend fun getTagsForReposInOrgs(orgUrl: String) =
37+
HttpAsyncClients.createDefault().use { client ->
38+
client.start()
39+
log.info("Getting repo list from $orgUrl")
40+
val resp = client.get(orgUrl, token)
41+
val repoList = mapper.readValue<List<Repo>>(resp.entity.content)
42+
repoList.map {
43+
log.info("Getting tags from ${it.tags_url}")
44+
val resp = client.get(it.tags_url, token)
45+
val name = it.name
46+
val tags = mapper.readValue<List<Tag>>(resp.entity.content).map {
47+
it.name
48+
}
49+
RepoAndTags(name, tags)
50+
}
51+
}
52+
53+
suspend fun showTagsForReposInOrgsUsingAsyncHttpClient(orgUrl: String) {
54+
DefaultAsyncHttpClient().use { client ->
55+
log.info("Getting repo list from $orgUrl")
56+
val resp = client.prepareGet(orgUrl).withToken()
57+
.execute().toCompletableFuture().await()
58+
val repoList = mapper.readValue<List<Repo>>(resp.responseBody)
59+
repoList.map {
60+
log.info("Getting tags from ${it.tags_url}")
61+
val resp = client.prepareGet(it.tags_url).withToken()
62+
.execute().toCompletableFuture().await()
63+
mapper.readValue<List<Tag>>(resp.responseBody).forEach {
64+
log.info("tag ${it.name} found")
65+
}
66+
}
67+
}
68+
}
69+
70+
private data class Repo(val name: String, val url: String, val tags_url: String)
71+
private data class Tag(val name: String)
72+
public data class RepoAndTags(val name: String, val tags: List<String>)
73+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import kotlinx.coroutines.experimental.*
4+
import org.slf4j.LoggerFactory
5+
import org.slf4j.MDC
6+
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
7+
import kotlin.coroutines.experimental.Continuation
8+
import kotlin.coroutines.experimental.ContinuationInterceptor
9+
import kotlin.coroutines.experimental.CoroutineContext
10+
11+
private val log = LoggerFactory.getLogger("intro")
12+
13+
fun main(args: Array<String>) = runBlocking {
14+
val key1 = "Key1"
15+
val key2 = "Key2"
16+
val key3 = "Key3"
17+
val context = CommonPool
18+
19+
val jobs = List(10) { ix ->
20+
MDC.put(key1, ix.toString())
21+
launch(preserveMDC(context)) {
22+
log.info("expected = {}, key1 = {}", ix, MDC.get(key1))
23+
MDC.put(key2, ix.toString())
24+
delay(1000)
25+
log.info("expected = {}, key1 = {}, key2 = {}", ix, MDC.get(key1), MDC.get(key2))
26+
MDC.put(key3, ix.toString())
27+
delay(1000)
28+
log.info("expected = {}, key1 = {}, key2 = {}, key3 = {}", ix, MDC.get(key1), MDC.get(key2), MDC.get(key3))
29+
}
30+
}
31+
jobs.forEach { it.join() }
32+
}
33+
34+
35+
fun preserveMDC(ctx: CoroutineContext): CoroutineContext {
36+
val currentInterceptor = ctx[ContinuationInterceptor.Key]
37+
if(currentInterceptor != null && currentInterceptor is Slf4jInterceptor) {
38+
//return ctx
39+
}
40+
val capturedMDC = MDC.getCopyOfContextMap()
41+
if(capturedMDC == null || capturedMDC.isEmpty()) {
42+
return ctx
43+
}
44+
val originalInterceptor = ctx[ContinuationInterceptor.Key] ?: NopInterceptor()
45+
return ctx + Slf4jInterceptor(originalInterceptor, capturedMDC)
46+
}
47+
48+
private class Slf4jInterceptor(val originalInterceptor: ContinuationInterceptor, val capturedContext: MutableMap<String, String>)
49+
: AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
50+
51+
var currentContext = capturedContext
52+
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
53+
originalInterceptor.interceptContinuation(object : Continuation<T> {
54+
55+
override val context: CoroutineContext
56+
get() = continuation.context
57+
58+
override fun resume(value: T) {
59+
currentContext = useMDC(currentContext) {
60+
continuation.resume(value)
61+
}
62+
}
63+
64+
override fun resumeWithException(exception: Throwable) {
65+
currentContext = useMDC(currentContext) {
66+
continuation.resumeWithException(exception)
67+
}
68+
}
69+
})
70+
71+
}
72+
73+
private class NopInterceptor : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
74+
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = continuation
75+
}
76+
77+
private inline fun <T> useMDC(mdc: MutableMap<String, String>, block: () -> T): MutableMap<String, String> {
78+
val original = MDC.getCopyOfContextMap()
79+
MDC.setContextMap(mdc)
80+
try {
81+
block()
82+
return MDC.getCopyOfContextMap()
83+
} finally {
84+
MDC.setContextMap(original)
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import org.slf4j.LoggerFactory
4+
5+
private val log = LoggerFactory.getLogger("intro")
6+
7+
suspend fun suspendFunctionWithDelay3(a: Int, b: Int): Int {
8+
log.info("step 1")
9+
delay(1000)
10+
log.info("step 2")
11+
delay(2000)
12+
log.info("step 3")
13+
return a + b
14+
}

‎src/main/resources/logback.xml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<configuration>
2+
3+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
4+
<!-- encoders are assigned the type
5+
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
6+
<encoder>
7+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
8+
</encoder>
9+
</appender>
10+
11+
<root level="info">
12+
<appender-ref ref="STDOUT" />
13+
</root>
14+
</configuration>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package pmhsfelix.kotlin.coroutines
2+
3+
import kotlinx.coroutines.experimental.delay
4+
import kotlinx.coroutines.experimental.runBlocking
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Test
7+
8+
class Tests0 {
9+
@Test
10+
fun testMySuspendingFunction() = runBlocking<Unit> {
11+
val v = 2
12+
delay(1000)
13+
assertEquals(2, v)
14+
}
15+
}
16+

0 commit comments

Comments
 (0)
Please sign in to comment.