Skip to content

Version 0.30.2 #684

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Oct 8, 2018
Merged
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change log for kotlinx.coroutines

## Version 0.30.2
* `Dispatchers.Main` is instantiated lazily (see #658 and #665).
* Blocking coroutine dispatcher views are now shutdown properly (#678).
* Prevent leaking Kotlin 1.3 from atomicfu dependency (#659).
* Thread-pool based dispatcher factories are marked as obsolete (#261).
* Fixed exception loss on `withContext` cancellation (#675).

## Version 0.30.1
Maintenance release:
* 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.
Expand Down
4 changes: 2 additions & 2 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ stable public API, and thus `kotlinx.coroutines` is leaving its "experimental" s
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.
In order to migrate `kotlinx.coroutines` to `1.0.0`, follow these steps:

1. Update `kotlinx.coroutines` to `0.30.1` version.
1. Update `kotlinx.coroutines` to `0.30.2` version.
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.
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.
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.
4. Update `kotlinx.coroutines` to version `1.0.0` or to the corresponding release candidate of it).

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![official JetBrains project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0)
[![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)
[![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)

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

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

```groovy
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.2'
}
```

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

```groovy
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.30.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.30.2'
```
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)
coroutine dispatcher and also makes sure that in case of crashed coroutine with unhandled exception this
Expand Down
33 changes: 33 additions & 0 deletions bump-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash

if [ "$#" -ne 2 ]
then
echo "Use: ./bump-version old_version new_version"
exit
fi

old_version=$1
new_version=$2

update_version() {
echo "Updating version from '$old_version' to '$new_version' in $1"
sed -i.bak s/$old_version/$new_version/g $1
rm $1.bak
}

update_version "README.md"
update_version "ui/coroutines-guide-ui.md"
update_version "ui/coroutines-guide-ui.md"
update_version "native/README.md"
update_version "ui/kotlinx-coroutines-android/example-app/gradle.properties"
update_version "ui/kotlinx-coroutines-android/animation-app/gradle.properties"
update_version "gradle.properties"

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)
if [ -z "$result" ];
then
echo "Done"
else
echo "Previous version is present in the project: $result"

fi
4 changes: 2 additions & 2 deletions common/kotlinx-coroutines-core-common/src/Builders.common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public suspend fun <T> withContext(
}
}
// SLOW PATH -- use new dispatcher
val coroutine = DispatchedCoroutine(newContext, uCont) // MODE_DISPATCHED
val coroutine = DispatchedCoroutine(newContext, uCont) // MODE_ATOMIC_DEFAULT
coroutine.initParentJob()
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
Expand Down Expand Up @@ -292,7 +292,7 @@ private class DispatchedCoroutine<in T>(
context: CoroutineContext,
uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
override val defaultResumeMode: Int get() = MODE_CANCELLABLE
override val defaultResumeMode: Int get() = MODE_ATOMIC_DEFAULT

// this is copy-and-paste of a decision state machine inside AbstractionContinuation
// todo: we may some-how abstract it via inline class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import kotlin.coroutines.experimental.*
/**
* Groups various implementations of [CoroutineDispatcher].
*/
expect object Dispatchers {
public expect object Dispatchers {
/**
* The default [CoroutineDispatcher] that is used by all standard builders like
* [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc
Expand Down
24 changes: 24 additions & 0 deletions common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,30 @@ class CoroutineScopeTest : TestBase() {
}
}

@Test
fun testCoroutineScopeCancellationVsException() = runTest {
expect(1)
var job: Job? = null
job = launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
try {
coroutineScope {
expect(3)
yield() // must suspend
expect(5)
job!!.cancel() // cancel this job _before_ it throws
throw TestException1()
}
} catch (e: TestException1) {
// must have caught TextException
expect(6)
}
}
expect(4)
yield() // to coroutineScope
finish(7)
}

@Test
fun testScopePlusContext() {
assertSame(EmptyCoroutineContext, scopePlusContext(EmptyCoroutineContext, EmptyCoroutineContext))
Expand Down
24 changes: 24 additions & 0 deletions common/kotlinx-coroutines-core-common/test/SupervisorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,30 @@ class SupervisorTest : TestBase() {
assertTrue(parent.isCancelled)
}

@Test
fun testSupervisorScopeCancellationVsException() = runTest {
expect(1)
var job: Job? = null
job = launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
try {
supervisorScope {
expect(3)
yield() // must suspend
expect(5)
job!!.cancel() // cancel this job _before_ it throws
throw TestException1()
}
} catch (e: TestException1) {
// must have caught TextException
expect(6)
}
}
expect(4)
yield() // to coroutineScope
finish(7)
}

private class TestException1 : Exception()
private class TestException2 : Exception()
}
54 changes: 54 additions & 0 deletions common/kotlinx-coroutines-core-common/test/WithContextTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,60 @@ class WithContextTest : TestBase() {
}
}

@Test
fun testRunCancellationUndispatchedVsException() = runTest {
expect(1)
var job: Job? = null
job = launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
try {
// Same dispatcher, different context
withContext(CoroutineName("testRunCancellationUndispatchedVsException")) {
expect(3)
yield() // must suspend
expect(5)
job!!.cancel() // cancel this job _before_ it throws
throw TestException()
}
} catch (e: TestException) {
// must have caught TextException
expect(6)
}
}
expect(4)
yield() // to coroutineScope
finish(7)
}

@Test
fun testRunCancellationDispatchedVsException() = runTest {
expect(1)
var job: Job? = null
job = launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
try {
// "Different" dispatcher (schedules execution back and forth)
withContext(wrapperDispatcher(coroutineContext)) {
expect(4)
yield() // must suspend
expect(6)
job!!.cancel() // cancel this job _before_ it throws
throw TestException()
}
} catch (e: TestException) {
// must have caught TextException
expect(8)
}
}
expect(3)
yield() // withContext is next
expect(5)
yield() // withContext again
expect(7)
yield() // to catch block
finish(9)
}

@Test
fun testRunSelfCancellationWithException() = runTest(unhandled = listOf({e -> e is AssertionError})) {
expect(1)
Expand Down
57 changes: 46 additions & 11 deletions core/kotlinx-coroutines-core/src/Dispatchers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,7 @@ public const val IO_PARALLELISM_PROPERTY_NAME = "kotlinx.coroutines.io.paralleli
/**
* Groups various implementations of [CoroutineDispatcher].
*/
actual object Dispatchers {

private val mainDispatcher = loadMainDispatcher()

private fun loadMainDispatcher(): MainCoroutineDispatcher? {
return MainDispatcherFactory::class.java.let { clz ->
ServiceLoader.load(clz, clz.classLoader).toList()
}.maxBy { it.loadPriority }?.createDispatcher()
}
public actual object Dispatchers {

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

/**
* A coroutine dispatcher that is not confined to any specific thread.
Expand Down Expand Up @@ -97,3 +88,47 @@ actual object Dispatchers {
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}

// Lazy loader for the main dispatcher
private object MainDispatcherLoader {
@JvmField
val dispatcher: MainCoroutineDispatcher =
MainDispatcherFactory::class.java.let { clz ->
ServiceLoader.load(clz, clz.classLoader).toList()
}.maxBy { it.loadPriority }?.tryCreateDispatcher() ?: MissingMainCoroutineDispatcher(null)

/**
* If anything goes wrong while trying to create main dispatcher (class not found,
* initialization failed, etc), then replace the main dispatcher with a special
* stub that throws an error message on any attempt to actually use it.
*/
private fun MainDispatcherFactory.tryCreateDispatcher(): MainCoroutineDispatcher =
try {
createDispatcher()
} catch (cause: Throwable) {
MissingMainCoroutineDispatcher(cause)
}
}

private class MissingMainCoroutineDispatcher(val cause: Throwable?) : MainCoroutineDispatcher(), Delay {
override val immediate: MainCoroutineDispatcher get() = this

override fun dispatch(context: CoroutineContext, block: Runnable) =
missing()

override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) =
missing()

private fun missing() {
if (cause == null) {
throw IllegalStateException(
"Module with the Main dispatcher is missing. " +
"Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android'"
)
} else {
throw IllegalStateException("Module with the Main dispatcher had failed to initialize", cause)
}
}

override fun toString(): String = "Main[missing${if (cause != null) ", cause=$cause" else ""}]"
}
22 changes: 22 additions & 0 deletions core/kotlinx-coroutines-core/src/ThreadPoolDispatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,19 @@ import kotlin.coroutines.experimental.*
* **NOTE: The resulting [ExecutorCoroutineDispatcher] owns native resources (its thread).
* Resources are reclaimed by [ExecutorCoroutineDispatcher.close].**
*
* **NOTE: This API will be replaced in the future**. A different API to create thread-limited thread pools
* that is based on a shared thread-pool and does not require the resulting dispatcher to be explicitly closed
* will be provided, thus avoiding potential thread leaks and also significantly improving performance, due
* to coroutine-oriented scheduling policy and thread-switch minimization.
* See [issue #261](https://github.com/Kotlin/kotlinx.coroutines/issues/261) for details.
* If you need a completely separate thread-pool with scheduling policy that is based on the standard
* JDK executors, use the following expression:
* `Executors.newSingleThreadExecutor().asCoroutineDispatcher()`.
* See [Executor.asCoroutineDispatcher] for details.
*
* @param name the base name of the created thread.
*/
@ObsoleteCoroutinesApi
fun newSingleThreadContext(name: String): ExecutorCoroutineDispatcher =
newFixedThreadPoolContext(1, name)

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