Skip to content

Commit cb57f3b

Browse files
elizarovdkhalanskyjbRoman Elizarov
committed
Build on JDK11
* Up the Robolectric version to support JDK11. According to robolectric/robolectric#4085, by 4.0.2 it should support JDK11. Tests do pass after setting the version to 4.0.2, but they fail for every version released after that up to 4.3.1. It is unclear what causes this. I commit this to check how it works on the build agents, as some comments in the issue imply that on MacOS thes version, too, does not work with JDK11. * Fix fully qualified names in stacktraces in tests: - With move to JDK11, the `park` method changed its fully qualified name. * Add new sanitazing to verification of stacktraces: - Now stacktraces have additional substrings, separated by a slash: java-base/java.util.lang - They are stripped away. - Also, the placement of tabs has changed, and so the tabs are also completely removed. * Refactor `verifyStackTrace` - It used to wrap the only loop where something happened in two other loops that did nothing. Now, only the innermost loop is left. * Use a separate JavaFx dependency. * Add javafx dependency to the Swing integration * Improve error handling for JavaFX initialization - Now, the JavaFX initialization may fail with an exception in case something went wrong. - The driver for this change was that the initialization started hanging in headless environments with transition to JDK 11. - Before, the initialization logic had a flaw. If a call to one API failed, another API would be attempted. However, this approach is problematic: if the first call failed with an exception for some reason, it would leave JavaFX in a broken state where a flag would imply that the system is being initialized. Subsequent calls would then proceed to wait forever for the initialization to complete. - Now, exceptions are checked more carefully, ensuring that we only fall back to the internal API in case the public one is unavailable and not failed for some valid reason. This differentiation also allows to more boldly rethrow exceptions upwards, being more or less confident that these are relevant to the user. * Additionally test JavaFX integration with JDK8 Co-authored-by: Dmitry Khalanskiy <[email protected]> Co-authored-by: Roman Elizarov <[email protected]>
1 parent 3cdbaf8 commit cb57f3b

File tree

9 files changed

+85
-43
lines changed

9 files changed

+85
-43
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,9 @@ to Gradle (in Preferences -> Build, Execution, Deployment -> Build Tools -> Grad
203203

204204
### Requirements
205205

206-
* JDK >= 1.8 referred to by the `JAVA_HOME` environment variable. JDK must include JavaFX.
206+
* JDK >= 11 referred to by the `JAVA_HOME` environment variable.
207207
* JDK 1.6 referred to by the `JDK_16` environment variable. It is okay to have `JDK_16` pointing to `JAVA_HOME` for external contributions.
208+
* JDK 1.8 referred to by the `JDK_18` environment variable. Only used by nightly stress-tests. It is okay to have `JDK_16` pointing to `JAVA_HOME` for external contributions.
208209

209210
## Contributions and releases
210211

gradle.properties

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ byte_buddy_version=1.9.3
1717
reactor_vesion=3.2.5.RELEASE
1818
reactive_streams_version=1.0.2
1919
rxjava2_version=2.2.8
20+
javafx_version=11.0.2
2021
binary_compatibility_validator_version=0.1.1
2122

2223
# JS

kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class RunningThreadStackMergeTest : DebugTestBase() {
2020
verifyDump(
2121
"Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored
2222
"Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@50284dc4, state: RUNNING\n" +
23-
"\tat sun.misc.Unsafe.park(Native Method)\n" +
23+
"\tat jdk.internal.misc.Unsafe.park(Native Method)\n" +
2424
"\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
2525
"\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
2626
"\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +
@@ -75,7 +75,7 @@ class RunningThreadStackMergeTest : DebugTestBase() {
7575
verifyDump(
7676
"Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored
7777
"Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" +
78-
"\tat sun.misc.Unsafe.park(Native Method)\n" +
78+
"\tat jdk.internal.misc.Unsafe.park(Native Method)\n" +
7979
"\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
8080
"\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
8181
"\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +
@@ -116,7 +116,7 @@ class RunningThreadStackMergeTest : DebugTestBase() {
116116
verifyDump(
117117
"Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored
118118
"Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" +
119-
"\tat sun.misc.Unsafe.park(Native Method)\n" +
119+
"\tat jdk.internal.misc.Unsafe.park(Native Method)\n" +
120120
"\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
121121
"\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
122122
"\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +

kotlinx-coroutines-debug/test/StracktraceUtils.kt

+7-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public fun String.trimStackTrace(): String =
1111
trimIndent()
1212
.replace(Regex(":[0-9]+"), "")
1313
.replace(Regex("#[0-9]+"), "")
14+
.replace(Regex("(?<=\tat )[^\n]*/"), "")
15+
.replace(Regex("\t"), "")
1416
.applyBackspace()
1517

1618
public fun String.applyBackspace(): String {
@@ -30,16 +32,12 @@ public fun String.applyBackspace(): String {
3032

3133
public fun verifyStackTrace(e: Throwable, traces: List<String>) {
3234
val stacktrace = toStackTrace(e)
35+
val trimmedStackTrace = stacktrace.trimStackTrace()
3336
traces.forEach {
34-
val expectedLines = it.trimStackTrace().split("\n")
35-
for (i in 0 until expectedLines.size) {
36-
traces.forEach {
37-
assertTrue(
38-
stacktrace.trimStackTrace().contains(it.trimStackTrace()),
39-
"\nExpected trace element:\n$it\n\nActual stacktrace:\n$stacktrace"
40-
)
41-
}
42-
}
37+
assertTrue(
38+
trimmedStackTrace.contains(it.trimStackTrace()),
39+
"\nExpected trace element:\n$it\n\nActual stacktrace:\n$stacktrace"
40+
)
4341
}
4442

4543
val causes = stacktrace.count("Caused by")

ui/kotlinx-coroutines-android/android-unit-tests/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ dependencies {
1010
testImplementation 'com.google.android:android:4.1.1.4'
1111
testImplementation 'com.android.support:support-annotations:26.1.0'
1212
testImplementation 'com.google.android:android:4.1.1.4'
13-
testImplementation 'org.robolectric:robolectric:4.0-alpha-3'
13+
testImplementation 'org.robolectric:robolectric:4.0.2'
1414
testImplementation project(":kotlinx-coroutines-test")
1515
testImplementation project(":kotlinx-coroutines-android")
1616
}

ui/kotlinx-coroutines-android/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ dependencies {
2222
compileOnly 'com.android.support:support-annotations:26.1.0'
2323

2424
testImplementation 'com.google.android:android:4.1.1.4'
25-
testImplementation 'org.robolectric:robolectric:4.0-alpha-3'
25+
testImplementation 'org.robolectric:robolectric:4.0.2'
2626
testImplementation 'org.smali:baksmali:2.2.7'
2727

2828
// TODO Replace with a 1.6.x version once released to maven.google.com.
@@ -105,4 +105,4 @@ tasks.withType(dokka.getClass()) {
105105
url = new URL("https://developer.android.com/reference/")
106106
packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
107107
}
108-
}
108+
}
+31-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,34 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
plugins {
6+
id 'org.openjfx.javafxplugin' version '0.0.8'
7+
}
8+
9+
javafx {
10+
version = javafx_version
11+
modules = [ 'javafx.controls' ]
12+
configuration = 'compile'
13+
}
14+
15+
task checkJdk8() {
16+
// only fail w/o JDK_18 when actually trying to test, not during project setup phase
17+
doLast {
18+
if (!System.env.JDK_18) {
19+
throw new GradleException("JDK_18 environment variable is not defined. " +
20+
"Can't run JDK 8 compatibility tests. " +
21+
"Please ensure JDK 8 is installed and that JDK_18 points to it.")
22+
}
23+
}
24+
}
25+
26+
task jdk8Test(type: Test, dependsOn: [compileTestKotlin, checkJdk8]) {
27+
classpath = files { test.classpath }
28+
testClassesDirs = files { test.testClassesDirs }
29+
executable = "$System.env.JDK_18/bin/java"
30+
}
31+
32+
// Run these tests only during nightly stress test
33+
jdk8Test.onlyIf { project.properties['stressTest'] != null }
34+
build.dependsOn jdk8Test

ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt

+27-25
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import javafx.util.*
1111
import kotlinx.coroutines.*
1212
import kotlinx.coroutines.internal.*
1313
import kotlinx.coroutines.javafx.JavaFx.delay
14+
import java.lang.UnsupportedOperationException
1415
import java.lang.reflect.*
1516
import java.util.concurrent.*
1617
import kotlin.coroutines.*
@@ -115,41 +116,42 @@ private class PulseTimer : AnimationTimer() {
115116
}
116117
}
117118

119+
/** @return [true] if initialized successfully, and [false] if no display is detected */
118120
internal fun initPlatform(): Boolean = PlatformInitializer.success
119121

120122
// Lazily try to initialize JavaFx platform just once
121123
private object PlatformInitializer {
122124
val success = run {
123125
/*
124126
* Try to instantiate JavaFx platform in a way which works
125-
* both on Java 8 and Java 11 and does not produce "illegal reflective access":
126-
*
127-
* 1) Try to invoke javafx.application.Platform.startup if this class is
128-
* present in a classpath.
129-
* 2) If it is not successful and does not because it is already started,
130-
* fallback to PlatformImpl.
131-
*
132-
* Ignore exception anyway in case of unexpected changes in API, in that case
133-
* user will have to instantiate it manually.
127+
* both on Java 8 and Java 11 and does not produce "illegal reflective access".
134128
*/
135-
val runnable = Runnable {}
136-
runCatching {
137-
// Invoke public API if it is present
138-
Class.forName("javafx.application.Platform")
139-
.getMethod("startup", java.lang.Runnable::class.java)
140-
.invoke(null, runnable)
141-
}.recoverCatching { exception ->
142-
// Recover -> check re-initialization
143-
val cause = exception.cause
144-
if (exception is InvocationTargetException && cause is IllegalStateException
145-
&& "Toolkit already initialized" == cause.message) {
146-
// Toolkit is already initialized -> success, return
147-
Unit
148-
} else { // Fallback to Java 8 API
149-
Class.forName("com.sun.javafx.application.PlatformImpl")
129+
try {
130+
val runnable = Runnable {}
131+
// Invoke the public API if it is present.
132+
runCatching {
133+
Class.forName("javafx.application.Platform")
134+
.getMethod("startup", java.lang.Runnable::class.java)
135+
}.map { method ->
136+
method.invoke(null, runnable)
137+
return@run true
138+
}
139+
// If we are here, it means the public API is not present. Try the private API.
140+
Class.forName("com.sun.javafx.application.PlatformImpl")
150141
.getMethod("startup", java.lang.Runnable::class.java)
151142
.invoke(null, runnable)
143+
true
144+
} catch (exception: InvocationTargetException) {
145+
// Can only happen as a result of [Method.invoke].
146+
val cause = exception.cause!!
147+
when {
148+
// Maybe the problem is that JavaFX is already initialized? Everything is good then.
149+
cause is IllegalStateException && "Toolkit already initialized" == cause.message -> true
150+
// If the problem is the headless environment, it is okay.
151+
cause is UnsupportedOperationException && "Unable to open DISPLAY" == cause.message -> false
152+
// Otherwise, the exception demonstrates an anomaly.
153+
else -> throw cause
152154
}
153-
}.isSuccess
155+
}
154156
}
155157
}

ui/kotlinx-coroutines-swing/build.gradle

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5+
plugins {
6+
id 'org.openjfx.javafxplugin' version '0.0.8'
7+
}
8+
9+
javafx {
10+
version = javafx_version
11+
modules = [ 'javafx.controls' ]
12+
configuration = 'compile'
13+
}
14+
515
dependencies {
616
testCompile project(':kotlinx-coroutines-jdk8')
7-
}
17+
}

0 commit comments

Comments
 (0)