Skip to content

Commit fc6461f

Browse files
committed
Contribution guidelines for integrations and improved JDK8 readme
1 parent d24ad36 commit fc6461f

File tree

4 files changed

+124
-30
lines changed

4 files changed

+124
-30
lines changed

integration/README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,16 @@ This directory contains modules that provide integration with various asynchrono
44

55
## Modules
66

7-
* [kotlinx-coroutines-jdk8](kotlinx-coroutines-jdk8) -- extensions for JDK8 `CompletableFuture` (Android API level 24).
8-
* [kotlinx-coroutines-nio](kotlinx-coroutines-nio) -- extensions for asynchronous IO on JDK7+ (Android O Preview).
7+
* [kotlinx-coroutines-jdk8](kotlinx-coroutines-jdk8/README.md) -- extensions for JDK8 `CompletableFuture` (Android API level 24).
8+
* [kotlinx-coroutines-nio](kotlinx-coroutines-nio/README.md) -- extensions for asynchronous IO on JDK7+ (Android O Preview).
9+
10+
## Contributing
11+
12+
Follow the following simple guidelines when contributing integration with your favorite library:
13+
14+
* Keep it simple and general. Ideally it should fit into a single file. If it does not fit, then consider
15+
a separate GitHub project to host this integration.
16+
* Follow the example of other modules. Don't fear to cut-and-paste [kotlinx-coroutines-jdk8](kotlinx-coroutines-jdk8)
17+
module for a start.
18+
* Write tests and documentation, include top-level README.md with short overview and example.
19+
* Include it into the list of modules in this file and to the top-level [pom.xml](../pom.xml).

integration/kotlinx-coroutines-jdk8/README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,39 @@ Extension functions:
1212

1313
| **Name** | **Description**
1414
| -------- | ---------------
15-
| [CompletableFuture.await][java.util.concurrent.CompletableFuture.await] | Awaits for completion of the future
15+
| [CompletionStage.await][java.util.concurrent.CompletionStage.await] | Awaits for completion of the completion stage (non-cancellable)
16+
| [CompletableFuture.await][java.util.concurrent.CompletableFuture.await] | Awaits for completion of the future (cancellable)
1617
| [Deferred.asCompletableFuture][kotlinx.coroutines.experimental.Deferred.asCompletableFuture] | Converts a deferred value to the future
1718

19+
## Example
20+
21+
Given the following functions defined in some Java API:
22+
23+
```java
24+
public CompletableFuture<Image> loadImageAsync(String name); // starts async image loading
25+
public Image combineImages(Image image1, Image image2); // synchronously combines two images using some algorithm
26+
```
27+
28+
We can consume this API from Kotlin coroutine to load two images and combine then asynchronously.
29+
The resulting function returns `CompletableFuture<Image>` for ease of use back from Java.
30+
31+
```kotlin
32+
fun combineImagesAsync(name1: String, name2: String): CompletableFuture<Image> = future {
33+
val future1 = loadImageAsync(name1) // start loading first image
34+
val future2 = loadImageAsync(name2) // start loading second image
35+
combineImages(future1.await(), future2.await()) // wait for both, combine, and return result
36+
}
37+
```
38+
39+
Note, that this module should be used only for integration with existing Java APIs based on `CompletableFuture`.
40+
Writing pure-Kotlin code that uses `CompletableFuture` is highly not recommended, since the resulting APIs based
41+
on the futures are quite error-prone. See the discussion on
42+
[Asynchronous Programming Styles](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#asynchronous-programming-styles)
43+
for details on general problems pertaining to any future-based API and keep in mind that `CompletableFuture` exposes
44+
a _blocking_ method
45+
[get](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html#get--)
46+
that makes it especially bad choice for coroutine-based Kotlin code.
47+
1848
# Package kotlinx.coroutines.experimental.future
1949

2050
Additional libraries for JDK8 [CompletableFuture][java.util.concurrent.CompletableFuture].
@@ -24,10 +54,11 @@ Additional libraries for JDK8 [CompletableFuture][java.util.concurrent.Completab
2454
<!--- INDEX kotlinx.coroutines.experimental -->
2555
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-scope/index.html
2656
<!--- SITE_ROOT https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8 -->
27-
<!--- DOCS_ROOT kotlinx-coroutines-jdk8/target/dokka/kotlinx-coroutines-jdk8 -->
57+
<!--- DOCS_ROOT integration/kotlinx-coroutines-jdk8/target/dokka/kotlinx-coroutines-jdk8 -->
2858
<!--- INDEX kotlinx.coroutines.experimental.future -->
2959
[future]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.experimental.future/future.html
3060
[java.util.concurrent.CompletableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.experimental.future/java.util.concurrent.-completable-future/index.html
61+
[java.util.concurrent.CompletionStage.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.experimental.future/java.util.concurrent.-completion-stage/await.html
3162
[java.util.concurrent.CompletableFuture.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.experimental.future/java.util.concurrent.-completable-future/await.html
3263
[kotlinx.coroutines.experimental.Deferred.asCompletableFuture]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.experimental.future/kotlinx.coroutines.experimental.-deferred/as-completable-future.html
3364
<!--- END -->

integration/kotlinx-coroutines-jdk8/src/main/kotlin/kotlinx/coroutines/experimental/future/Future.kt

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package kotlinx.coroutines.experimental.future
1818

1919
import kotlinx.coroutines.experimental.*
2020
import java.util.concurrent.CompletableFuture
21+
import java.util.concurrent.CompletionStage
2122
import kotlin.coroutines.experimental.Continuation
2223
import kotlin.coroutines.experimental.CoroutineContext
2324
import kotlin.coroutines.experimental.startCoroutine
25+
import kotlin.coroutines.experimental.suspendCoroutine
2426

2527
/**
2628
* Starts new coroutine and returns its results an an implementation of [CompletableFuture].
@@ -44,16 +46,6 @@ public fun <T> future(context: CoroutineContext = CommonPool, block: suspend ()
4446
return future
4547
}
4648

47-
48-
/**
49-
* Converts this deferred value to the instance of [CompletableFuture].
50-
* The deferred value is cancelled when the resulting future is cancelled or otherwise completed.
51-
* @suppress: **Deprecated**: Renamed to [asCompletableFuture]
52-
*/
53-
@Deprecated("Renamed to `asCompletableFuture`",
54-
replaceWith = ReplaceWith("asCompletableFuture()"))
55-
public fun <T> Deferred<T>.toCompletableFuture(): CompletableFuture<T> = asCompletableFuture()
56-
5749
/**
5850
* Converts this deferred value to the instance of [CompletableFuture].
5951
* The deferred value is cancelled when the resulting future is cancelled or otherwise completed.
@@ -71,16 +63,31 @@ public fun <T> Deferred<T>.asCompletableFuture(): CompletableFuture<T> {
7163
return future
7264
}
7365

66+
/**
67+
* Awaits for completion of the completion stage without blocking a thread.
68+
*
69+
* This suspending function is not cancellable, because there is no way to cancel a `CompletionStage`.
70+
* Use `CompletableFuture.await()` for cancellation support.
71+
*/
72+
public suspend fun <T> CompletionStage<T>.await(): T = suspendCoroutine { cont: Continuation<T> ->
73+
whenComplete { result, exception ->
74+
if (exception == null) // the stage has been completed normally
75+
cont.resume(result)
76+
else // the stage has completed with an exception
77+
cont.resumeWithException(exception)
78+
}
79+
}
80+
7481
/**
7582
* Awaits for completion of the future without blocking a thread.
7683
*
7784
* This suspending function is cancellable.
7885
* If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
79-
* immediately resumes with [CancellationException] .
86+
* cancels the `CompletableFuture` and immediately resumes with [CancellationException] .
8087
*/
8188
public suspend fun <T> CompletableFuture<T>.await(): T {
82-
if (isDone) {
83-
// then only way to get unwrapped exception from the CompletableFuture...
89+
if (isDone) { // fast path when CompletableFuture is already done (does not suspend)
90+
// then only way to get unwrapped exception from the CompletableFuture is via whenComplete anyway
8491
var result: T? = null
8592
var exception: Throwable? = null
8693
whenComplete { r, e ->
@@ -90,6 +97,7 @@ public suspend fun <T> CompletableFuture<T>.await(): T {
9097
if (exception != null) throw exception!!
9198
return result as T
9299
}
100+
// slow path -- suspend
93101
return suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
94102
val completionFuture = whenComplete { result, exception ->
95103
if (exception == null) // the future has been completed normally
@@ -98,7 +106,6 @@ public suspend fun <T> CompletableFuture<T>.await(): T {
98106
cont.resumeWithException(exception)
99107
}
100108
cont.cancelFutureOnCompletion(completionFuture)
101-
Unit
102109
}
103110
}
104111

@@ -108,3 +115,16 @@ private class CompletableFutureCoroutine<T>(
108115
override fun resume(value: T) { complete(value) }
109116
override fun resumeWithException(exception: Throwable) { completeExceptionally(exception) }
110117
}
118+
119+
// --------------------------------------- DEPRECATED APIs ---------------------------------------
120+
// We keep it only for backwards compatibility with old versions of this integration library.
121+
// Do not copy when using this file an example for other integration.
122+
123+
/**
124+
* Converts this deferred value to the instance of [CompletableFuture].
125+
* The deferred value is cancelled when the resulting future is cancelled or otherwise completed.
126+
* @suppress: **Deprecated**: Renamed to [asCompletableFuture]
127+
*/
128+
@Deprecated("Renamed to `asCompletableFuture`",
129+
replaceWith = ReplaceWith("asCompletableFuture()"))
130+
public fun <T> Deferred<T>.toCompletableFuture(): CompletableFuture<T> = asCompletableFuture()

integration/kotlinx-coroutines-jdk8/src/test/kotlin/kotlinx/coroutines/experimental/future/FutureTest.kt

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import java.util.concurrent.ExecutionException
2323
import java.util.concurrent.atomic.AtomicInteger
2424
import kotlin.coroutines.experimental.CoroutineContext
2525
import org.junit.Assert.*
26+
import java.util.concurrent.CompletionStage
2627

2728
class FutureTest {
2829
@Test
@@ -32,25 +33,34 @@ class FutureTest {
3233
"O"
3334
}.await() + "K"
3435
}
35-
3636
assertEquals("OK", future.get())
3737
}
3838

3939
@Test
40-
fun testWaitForCompletion() {
40+
fun testWaitForFuture() {
4141
val toAwait = CompletableFuture<String>()
4242
val future = future {
4343
toAwait.await() + "K"
4444
}
45-
4645
assertFalse(future.isDone)
4746
toAwait.complete("O")
47+
assertEquals("OK", future.get())
48+
}
4849

50+
@Test
51+
fun testWaitForCompletionStage() {
52+
val completable = CompletableFuture<String>()
53+
val toAwait: CompletionStage<String> = completable
54+
val future = future {
55+
toAwait.await() + "K"
56+
}
57+
assertFalse(future.isDone)
58+
completable.complete("O")
4959
assertEquals("OK", future.get())
5060
}
5161

5262
@Test
53-
fun testDoneFutureCompletedExceptionally() {
63+
fun testDoneFutureExceptionally() {
5464
val toAwait = CompletableFuture<String>()
5565
toAwait.completeExceptionally(RuntimeException("O"))
5666
val future = future<String> {
@@ -63,6 +73,21 @@ class FutureTest {
6373
assertEquals("OK", future.get())
6474
}
6575

76+
@Test
77+
fun testDoneCompletionStageExceptionally() {
78+
val completable = CompletableFuture<String>()
79+
val toAwait: CompletionStage<String> = completable
80+
completable.completeExceptionally(RuntimeException("O"))
81+
val future = future<String> {
82+
try {
83+
toAwait.await()
84+
} catch (e: RuntimeException) {
85+
e.message!!
86+
} + "K"
87+
}
88+
assertEquals("OK", future.get())
89+
}
90+
6691
@Test
6792
fun testAwaitedFutureCompletedExceptionally() {
6893
val toAwait = CompletableFuture<String>()
@@ -73,10 +98,24 @@ class FutureTest {
7398
e.message!!
7499
} + "K"
75100
}
76-
77101
assertFalse(future.isDone)
78102
toAwait.completeExceptionally(RuntimeException("O"))
103+
assertEquals("OK", future.get())
104+
}
79105

106+
@Test
107+
fun testAwaitedCompletionStageCompletedExceptionally() {
108+
val completable = CompletableFuture<String>()
109+
val toAwait: CompletionStage<String> = completable
110+
val future = future<String> {
111+
try {
112+
toAwait.await()
113+
} catch (e: RuntimeException) {
114+
e.message!!
115+
} + "K"
116+
}
117+
assertFalse(future.isDone)
118+
completable.completeExceptionally(RuntimeException("O"))
80119
assertEquals("OK", future.get())
81120
}
82121

@@ -88,7 +127,6 @@ class FutureTest {
88127
}
89128
CompletableFuture.supplyAsync { "fail" }.await()
90129
}
91-
92130
try {
93131
future.get()
94132
fail("'get' should've throw an exception")
@@ -101,32 +139,26 @@ class FutureTest {
101139
@Test
102140
fun testContinuationWrapped() {
103141
val depth = AtomicInteger()
104-
105142
val future = future(wrapContinuation {
106143
depth.andIncrement
107144
it()
108145
depth.andDecrement
109146
}) {
110147
assertEquals("Part before first suspension must be wrapped", 1, depth.get())
111-
112148
val result =
113149
CompletableFuture.supplyAsync {
114150
while (depth.get() > 0) ;
115151
assertEquals("Part inside suspension point should not be wrapped", 0, depth.get())
116152
"OK"
117153
}.await()
118-
119154
assertEquals("Part after first suspension should be wrapped", 1, depth.get())
120-
121155
CompletableFuture.supplyAsync {
122156
while (depth.get() > 0) ;
123157
assertEquals("Part inside suspension point should not be wrapped", 0, depth.get())
124158
"ignored"
125159
}.await()
126-
127160
result
128161
}
129-
130162
assertEquals("OK", future.get())
131163
}
132164

0 commit comments

Comments
 (0)