Skip to content

Commit 305ac20

Browse files
authored
Merge pull request #684 from Kotlin/version-0.30.2
Version 0.30.2
2 parents e6b4739 + a6199a8 commit 305ac20

File tree

25 files changed

+277
-42
lines changed

25 files changed

+277
-42
lines changed

CHANGES.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Change log for kotlinx.coroutines
22

3+
## Version 0.30.2
4+
* `Dispatchers.Main` is instantiated lazily (see #658 and #665).
5+
* Blocking coroutine dispatcher views are now shutdown properly (#678).
6+
* Prevent leaking Kotlin 1.3 from atomicfu dependency (#659).
7+
* Thread-pool based dispatcher factories are marked as obsolete (#261).
8+
* Fixed exception loss on `withContext` cancellation (#675).
9+
310
## Version 0.30.1
411
Maintenance release:
512
* Added `Dispatchers.Main` to common dispatchers, which can be used from Android, Swing and JavaFx projects if a corresponding integration library is added to dependencies.

COMPATIBILITY.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ stable public API, and thus `kotlinx.coroutines` is leaving its "experimental" s
1818
Version `1.0.0` (starting with its release candidate build) will have all its deprecated declarations removed and `kotlinx.coroutines.experimental` package will be renamed to `kotlinx.coroutines` without functional changes.
1919
In order to migrate `kotlinx.coroutines` to `1.0.0`, follow these steps:
2020

21-
1. Update `kotlinx.coroutines` to `0.30.1` version.
21+
1. Update `kotlinx.coroutines` to `0.30.2` version.
2222
2. Inspect compiler warnings about deprecated API and migrate it to a proposed alternative. Most of deprecated API has a corresponding replacement which can be applied from IDEA with quickfix.
23-
3. Update Kotlin version to `1.3.0` or to the latest `1.3.0-rc` and `kotlinx.coroutines` to version `0.30.1-eap13`. Then just get rid of `experimental` suffix in all imports.
23+
3. Update Kotlin version to `1.3.0` or to the latest `1.3.0-rc` and `kotlinx.coroutines` to version `0.30.2-eap13`. Then just get rid of `experimental` suffix in all imports.
2424
4. Update `kotlinx.coroutines` to version `1.0.0` or to the corresponding release candidate of it).
2525

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![official JetBrains project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
44
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0)
5-
[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=0.30.1) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/0.30.1)
5+
[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=0.30.2) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/0.30.2)
66

77
Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
88
This is a companion version for Kotlin 1.2.70 release.
@@ -68,7 +68,7 @@ Add dependencies (you can also add other modules that you need):
6868
<dependency>
6969
<groupId>org.jetbrains.kotlinx</groupId>
7070
<artifactId>kotlinx-coroutines-core</artifactId>
71-
<version>0.30.1</version>
71+
<version>0.30.2</version>
7272
</dependency>
7373
```
7474

@@ -86,7 +86,7 @@ Add dependencies (you can also add other modules that you need):
8686

8787
```groovy
8888
dependencies {
89-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.1'
89+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.2'
9090
}
9191
```
9292

@@ -120,7 +120,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
120120
module as dependency when using `kotlinx.coroutines` on Android:
121121

122122
```groovy
123-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.30.1'
123+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.30.2'
124124
```
125125
This gives you access to Android [Dispatchers.Main](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-android/kotlinx.coroutines.experimental.android/kotlinx.coroutines.experimental.-dispatchers/index.html)
126126
coroutine dispatcher and also makes sure that in case of crashed coroutine with unhandled exception this

bump-version.sh

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/bin/bash
2+
3+
if [ "$#" -ne 2 ]
4+
then
5+
echo "Use: ./bump-version old_version new_version"
6+
exit
7+
fi
8+
9+
old_version=$1
10+
new_version=$2
11+
12+
update_version() {
13+
echo "Updating version from '$old_version' to '$new_version' in $1"
14+
sed -i.bak s/$old_version/$new_version/g $1
15+
rm $1.bak
16+
}
17+
18+
update_version "README.md"
19+
update_version "ui/coroutines-guide-ui.md"
20+
update_version "ui/coroutines-guide-ui.md"
21+
update_version "native/README.md"
22+
update_version "ui/kotlinx-coroutines-android/example-app/gradle.properties"
23+
update_version "ui/kotlinx-coroutines-android/animation-app/gradle.properties"
24+
update_version "gradle.properties"
25+
26+
result=$(find ./ -type f \( -iname \*.properties -o -iname \*.md \) | grep -v "\.gradle" | grep -v "build" | xargs -I{} grep -H "$old_version" {} | grep -v CHANGES.md)
27+
if [ -z "$result" ];
28+
then
29+
echo "Done"
30+
else
31+
echo "Previous version is present in the project: $result"
32+
33+
fi

common/kotlinx-coroutines-core-common/src/Builders.common.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public suspend fun <T> withContext(
214214
}
215215
}
216216
// SLOW PATH -- use new dispatcher
217-
val coroutine = DispatchedCoroutine(newContext, uCont) // MODE_DISPATCHED
217+
val coroutine = DispatchedCoroutine(newContext, uCont) // MODE_ATOMIC_DEFAULT
218218
coroutine.initParentJob()
219219
block.startCoroutineCancellable(coroutine, coroutine)
220220
coroutine.getResult()
@@ -292,7 +292,7 @@ private class DispatchedCoroutine<in T>(
292292
context: CoroutineContext,
293293
uCont: Continuation<T>
294294
) : ScopeCoroutine<T>(context, uCont) {
295-
override val defaultResumeMode: Int get() = MODE_CANCELLABLE
295+
override val defaultResumeMode: Int get() = MODE_ATOMIC_DEFAULT
296296

297297
// this is copy-and-paste of a decision state machine inside AbstractionContinuation
298298
// todo: we may some-how abstract it via inline class

common/kotlinx-coroutines-core-common/src/Dispatchers.common.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import kotlin.coroutines.experimental.*
99
/**
1010
* Groups various implementations of [CoroutineDispatcher].
1111
*/
12-
expect object Dispatchers {
12+
public expect object Dispatchers {
1313
/**
1414
* The default [CoroutineDispatcher] that is used by all standard builders like
1515
* [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc

common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt

+24
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,30 @@ class CoroutineScopeTest : TestBase() {
219219
}
220220
}
221221

222+
@Test
223+
fun testCoroutineScopeCancellationVsException() = runTest {
224+
expect(1)
225+
var job: Job? = null
226+
job = launch(start = CoroutineStart.UNDISPATCHED) {
227+
expect(2)
228+
try {
229+
coroutineScope {
230+
expect(3)
231+
yield() // must suspend
232+
expect(5)
233+
job!!.cancel() // cancel this job _before_ it throws
234+
throw TestException1()
235+
}
236+
} catch (e: TestException1) {
237+
// must have caught TextException
238+
expect(6)
239+
}
240+
}
241+
expect(4)
242+
yield() // to coroutineScope
243+
finish(7)
244+
}
245+
222246
@Test
223247
fun testScopePlusContext() {
224248
assertSame(EmptyCoroutineContext, scopePlusContext(EmptyCoroutineContext, EmptyCoroutineContext))

common/kotlinx-coroutines-core-common/test/SupervisorTest.kt

+24
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,30 @@ class SupervisorTest : TestBase() {
195195
assertTrue(parent.isCancelled)
196196
}
197197

198+
@Test
199+
fun testSupervisorScopeCancellationVsException() = runTest {
200+
expect(1)
201+
var job: Job? = null
202+
job = launch(start = CoroutineStart.UNDISPATCHED) {
203+
expect(2)
204+
try {
205+
supervisorScope {
206+
expect(3)
207+
yield() // must suspend
208+
expect(5)
209+
job!!.cancel() // cancel this job _before_ it throws
210+
throw TestException1()
211+
}
212+
} catch (e: TestException1) {
213+
// must have caught TextException
214+
expect(6)
215+
}
216+
}
217+
expect(4)
218+
yield() // to coroutineScope
219+
finish(7)
220+
}
221+
198222
private class TestException1 : Exception()
199223
private class TestException2 : Exception()
200224
}

common/kotlinx-coroutines-core-common/test/WithContextTest.kt

+54
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,60 @@ class WithContextTest : TestBase() {
139139
}
140140
}
141141

142+
@Test
143+
fun testRunCancellationUndispatchedVsException() = runTest {
144+
expect(1)
145+
var job: Job? = null
146+
job = launch(start = CoroutineStart.UNDISPATCHED) {
147+
expect(2)
148+
try {
149+
// Same dispatcher, different context
150+
withContext(CoroutineName("testRunCancellationUndispatchedVsException")) {
151+
expect(3)
152+
yield() // must suspend
153+
expect(5)
154+
job!!.cancel() // cancel this job _before_ it throws
155+
throw TestException()
156+
}
157+
} catch (e: TestException) {
158+
// must have caught TextException
159+
expect(6)
160+
}
161+
}
162+
expect(4)
163+
yield() // to coroutineScope
164+
finish(7)
165+
}
166+
167+
@Test
168+
fun testRunCancellationDispatchedVsException() = runTest {
169+
expect(1)
170+
var job: Job? = null
171+
job = launch(start = CoroutineStart.UNDISPATCHED) {
172+
expect(2)
173+
try {
174+
// "Different" dispatcher (schedules execution back and forth)
175+
withContext(wrapperDispatcher(coroutineContext)) {
176+
expect(4)
177+
yield() // must suspend
178+
expect(6)
179+
job!!.cancel() // cancel this job _before_ it throws
180+
throw TestException()
181+
}
182+
} catch (e: TestException) {
183+
// must have caught TextException
184+
expect(8)
185+
}
186+
}
187+
expect(3)
188+
yield() // withContext is next
189+
expect(5)
190+
yield() // withContext again
191+
expect(7)
192+
yield() // to catch block
193+
finish(9)
194+
}
195+
142196
@Test
143197
fun testRunSelfCancellationWithException() = runTest(unhandled = listOf({e -> e is AssertionError})) {
144198
expect(1)

core/kotlinx-coroutines-core/src/Dispatchers.kt

+46-11
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,7 @@ public const val IO_PARALLELISM_PROPERTY_NAME = "kotlinx.coroutines.io.paralleli
1919
/**
2020
* Groups various implementations of [CoroutineDispatcher].
2121
*/
22-
actual object Dispatchers {
23-
24-
private val mainDispatcher = loadMainDispatcher()
25-
26-
private fun loadMainDispatcher(): MainCoroutineDispatcher? {
27-
return MainDispatcherFactory::class.java.let { clz ->
28-
ServiceLoader.load(clz, clz.classLoader).toList()
29-
}.maxBy { it.loadPriority }?.createDispatcher()
30-
}
22+
public actual object Dispatchers {
3123

3224
/**
3325
* The default [CoroutineDispatcher] that is used by all standard builders like
@@ -59,8 +51,7 @@ actual object Dispatchers {
5951
* Implementation note: [MainCoroutineDispatcher.immediate] is not supported on Native and JS platforms.
6052
*/
6153
@JvmStatic
62-
public actual val Main: MainCoroutineDispatcher get() = mainDispatcher ?: error("Module with Main dispatcher is missing. " +
63-
"Add dependency with required Main dispatcher, e.g. 'kotlinx-coroutines-android'")
54+
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
6455

6556
/**
6657
* A coroutine dispatcher that is not confined to any specific thread.
@@ -97,3 +88,47 @@ actual object Dispatchers {
9788
@JvmStatic
9889
public val IO: CoroutineDispatcher = DefaultScheduler.IO
9990
}
91+
92+
// Lazy loader for the main dispatcher
93+
private object MainDispatcherLoader {
94+
@JvmField
95+
val dispatcher: MainCoroutineDispatcher =
96+
MainDispatcherFactory::class.java.let { clz ->
97+
ServiceLoader.load(clz, clz.classLoader).toList()
98+
}.maxBy { it.loadPriority }?.tryCreateDispatcher() ?: MissingMainCoroutineDispatcher(null)
99+
100+
/**
101+
* If anything goes wrong while trying to create main dispatcher (class not found,
102+
* initialization failed, etc), then replace the main dispatcher with a special
103+
* stub that throws an error message on any attempt to actually use it.
104+
*/
105+
private fun MainDispatcherFactory.tryCreateDispatcher(): MainCoroutineDispatcher =
106+
try {
107+
createDispatcher()
108+
} catch (cause: Throwable) {
109+
MissingMainCoroutineDispatcher(cause)
110+
}
111+
}
112+
113+
private class MissingMainCoroutineDispatcher(val cause: Throwable?) : MainCoroutineDispatcher(), Delay {
114+
override val immediate: MainCoroutineDispatcher get() = this
115+
116+
override fun dispatch(context: CoroutineContext, block: Runnable) =
117+
missing()
118+
119+
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) =
120+
missing()
121+
122+
private fun missing() {
123+
if (cause == null) {
124+
throw IllegalStateException(
125+
"Module with the Main dispatcher is missing. " +
126+
"Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android'"
127+
)
128+
} else {
129+
throw IllegalStateException("Module with the Main dispatcher had failed to initialize", cause)
130+
}
131+
}
132+
133+
override fun toString(): String = "Main[missing${if (cause != null) ", cause=$cause" else ""}]"
134+
}

core/kotlinx-coroutines-core/src/ThreadPoolDispatcher.kt

+22
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,19 @@ import kotlin.coroutines.experimental.*
1414
* **NOTE: The resulting [ExecutorCoroutineDispatcher] owns native resources (its thread).
1515
* Resources are reclaimed by [ExecutorCoroutineDispatcher.close].**
1616
*
17+
* **NOTE: This API will be replaced in the future**. A different API to create thread-limited thread pools
18+
* that is based on a shared thread-pool and does not require the resulting dispatcher to be explicitly closed
19+
* will be provided, thus avoiding potential thread leaks and also significantly improving performance, due
20+
* to coroutine-oriented scheduling policy and thread-switch minimization.
21+
* See [issue #261](https://github.com/Kotlin/kotlinx.coroutines/issues/261) for details.
22+
* If you need a completely separate thread-pool with scheduling policy that is based on the standard
23+
* JDK executors, use the following expression:
24+
* `Executors.newSingleThreadExecutor().asCoroutineDispatcher()`.
25+
* See [Executor.asCoroutineDispatcher] for details.
26+
*
1727
* @param name the base name of the created thread.
1828
*/
29+
@ObsoleteCoroutinesApi
1930
fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher =
2031
newFixedThreadPoolContext(1, name)
2132

@@ -40,9 +51,20 @@ fun newSingleThreadContext(name: String, parent: Job? = null): CoroutineContext
4051
* **NOTE: The resulting [ExecutorCoroutineDispatcher] owns native resources (its threads).
4152
* Resources are reclaimed by [ExecutorCoroutineDispatcher.close].**
4253
*
54+
* **NOTE: This API will be replaced in the future**. A different API to create thread-limited thread pools
55+
* that is based on a shared thread-pool and does not require the resulting dispatcher to be explicitly closed
56+
* will be provided, thus avoiding potential thread leaks and also significantly improving performance, due
57+
* to coroutine-oriented scheduling policy and thread-switch minimization.
58+
* See [issue #261](https://github.com/Kotlin/kotlinx.coroutines/issues/261) for details.
59+
* If you need a completely separate thread-pool with scheduling policy that is based on the standard
60+
* JDK executors, use the following expression:
61+
* `Executors.newFixedThreadPool().asCoroutineDispatcher()`.
62+
* See [Executor.asCoroutineDispatcher] for details.
63+
*
4364
* @param nThreads the number of threads.
4465
* @param name the base name of the created threads.
4566
*/
67+
@ObsoleteCoroutinesApi
4668
fun newFixedThreadPoolContext(nThreads: Int, name: String): ExecutorCoroutineDispatcher {
4769
require(nThreads >= 1) { "Expected at least one thread, but $nThreads specified" }
4870
return ThreadPoolDispatcher(nThreads, name)

0 commit comments

Comments
 (0)