Skip to content

Commit 273eab0

Browse files
authored
Merge pull request #80 from cljohnso/testJava21
Enable building & testing with Java 21
2 parents 08d6f4d + bdd96d4 commit 273eab0

File tree

9 files changed

+156
-18
lines changed

9 files changed

+156
-18
lines changed

.github/workflows/ci.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
image: almalinux:latest
3030
strategy:
3131
matrix:
32-
java: [ 8, 11, 17 ]
32+
java: [ 8, 11, 17, 21 ]
3333
distribution: [ zulu ]
3434
fail-fast: false
3535
max-parallel: 4
@@ -63,7 +63,7 @@ jobs:
6363
options: --user 1001:1001
6464
strategy:
6565
matrix:
66-
java: [ 8, 11, 17 ]
66+
java: [ 8, 11, 17, 21 ]
6767
distribution: [ zulu ]
6868
fail-fast: false
6969
max-parallel: 4
@@ -99,7 +99,7 @@ jobs:
9999
strategy:
100100
matrix:
101101
os: [ ubuntu-latest, macOS-latest, windows-latest ]
102-
java: [ 8, 11, 17 ]
102+
java: [ 8, 11, 17, 21 ]
103103
distribution: [ zulu ]
104104
fail-fast: false
105105
max-parallel: 4

buildSrc/src/main/kotlin/org.terracotta.java-conventions.gradle.kts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ tasks {
112112
options.encoding = "UTF-8"
113113
options.isDeprecation = true
114114
options.isWarnings = true
115-
options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))
115+
// '-Xlint:options' needed to ignore warning about source/target Java 8 obsolescence
116+
options.compilerArgs.addAll(listOf("-Xlint:all", "-Xlint:-options", "-Werror"))
116117
}
117118

118119
withType<Javadoc> {
@@ -145,6 +146,19 @@ tasks {
145146
}
146147
}
147148

149+
spotbugsMain {
150+
val spotbugsLastCheckLevel = JavaVersion.VERSION_21 // Most current Java version assessed for Spotbugs support
151+
val spotbugsSuppressionLevel = JavaVersion.VERSION_21 // Java version at which Spotbugs is suppressed
152+
if (JavaVersion.current() > spotbugsLastCheckLevel) {
153+
throw GradleException("Current version of Java exceeds Spotbugs suppression level; re-examine Spotbugs suppression", null)
154+
} else if (JavaVersion.current() >= spotbugsSuppressionLevel) {
155+
logger.warn("Suppressing Spotbugs; current Java Version ${JavaVersion.current()} >= Spotbugs support version ${spotbugsSuppressionLevel}")
156+
enabled = false
157+
} else {
158+
enabled = true
159+
}
160+
}
161+
148162
spotbugsTest {
149163
enabled = false
150164
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ defaultVersion=0.0.19-SNAPSHOT
66
# gradle release (or: gradle release -Prelease.useAutomaticVersion=true)
77
version=0.0.19-SNAPSHOT
88

9-
spotbugsAnnotationsVersion=4.0.2
9+
spotbugsAnnotationsVersion=4.8.3
1010

1111
slf4jBaseVersion=1.7.32
1212
slf4jUpperVersion=1.7.9999

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

gradlew

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,15 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145145
case $MAX_FD in #(
146146
max*)
147147
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148-
# shellcheck disable=SC3045
148+
# shellcheck disable=SC2039,SC3045
149149
MAX_FD=$( ulimit -H -n ) ||
150150
warn "Could not query maximum file descriptor limit"
151151
esac
152152
case $MAX_FD in #(
153153
'' | soft) :;; #(
154154
*)
155155
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156-
# shellcheck disable=SC3045
156+
# shellcheck disable=SC2039,SC3045
157157
ulimit -n "$MAX_FD" ||
158158
warn "Could not set maximum file descriptor limit to $MAX_FD"
159159
esac
@@ -202,11 +202,11 @@ fi
202202
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203203
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204204

205-
# Collect all arguments for the java command;
206-
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
207-
# shell script including quotes and variable substitutions, so put them in
208-
# double quotes to make sure that they get re-expanded; and
209-
# * put everything else in single quotes, so that it's not re-expanded.
205+
# Collect all arguments for the java command:
206+
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207+
# and any embedded shellness will be escaped.
208+
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209+
# treated as '${Hostname}' itself on the command line.
210210

211211
set -- \
212212
"-Dorg.gradle.appname=$APP_BASE_NAME" \

port-chooser/src/main/java/org/terracotta/utilities/test/net/NetStat.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.terracotta.utilities.test.net;
1717

18+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1819
import org.slf4j.Logger;
1920
import org.slf4j.LoggerFactory;
2021
import org.terracotta.utilities.exec.Shell;
@@ -1599,11 +1600,13 @@ public HostExecutionException(String message, Throwable cause) {
15991600
this.result = null;
16001601
}
16011602

1603+
@SuppressFBWarnings("EI_EXPOSE_REP2")
16021604
public HostExecutionException(String command, Shell.Result result, Throwable cause) {
16031605
super(message(command, result), cause);
16041606
this.result = result;
16051607
}
16061608

1609+
@SuppressFBWarnings("EI_EXPOSE_REP2")
16071610
public HostExecutionException(String command, Shell.Result result) {
16081611
super(message(command, result));
16091612
this.result = result;

test-tools/src/main/java/org/terracotta/utilities/test/Diagnostics.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import java.io.File;
2121
import java.io.IOException;
2222
import java.io.PrintStream;
23+
import java.lang.invoke.MethodHandle;
24+
import java.lang.invoke.MethodHandles;
25+
import java.lang.invoke.MethodType;
2326
import java.lang.management.BufferPoolMXBean;
2427
import java.lang.management.LockInfo;
2528
import java.lang.management.ManagementFactory;
@@ -88,7 +91,7 @@ public ThreadPair(Thread thread) {
8891
* terminated, its slot in the returned array is null.
8992
*/
9093
List<Thread> threadList = new ArrayList<>(threads);
91-
long[] threadIds = threadList.stream().mapToLong(Thread::getId).toArray();
94+
long[] threadIds = threadList.stream().mapToLong(ThreadId::get).toArray();
9295
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
9396
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds, true, true);
9497
for (int i = 0; i < threadList.size(); i++) {
@@ -102,7 +105,7 @@ public ThreadPair(Thread thread) {
102105
if (p.threadInfo == null) {
103106
Thread t = p.thread;
104107
printStream.format("\"%s\" Id=%d prio=%d %s%n",
105-
t.getName(), t.getId(), t.getPriority(), t.getState());
108+
t.getName(), ThreadId.get(t), t.getPriority(), t.getState());
106109
for (StackTraceElement element : t.getStackTrace()) {
107110
printStream.format("\tat %s%n", element);
108111
}
@@ -313,6 +316,17 @@ public static long getLongPid() {
313316
return Pid.PID.orElse(-1);
314317
}
315318

319+
/**
320+
* Gets the thread id of the specified thread. For Java 19+, this method returns the
321+
* value of {@code Thread.threadId}; for Java 18-, this method returns the value of
322+
* {@code Thread.getId}.
323+
* @param thread the {@code thread} for which the id is to be returned
324+
* @return the {@code Thread} id; {@code -1} if an error was raised while obtaining the id
325+
*/
326+
public static long threadId(Thread thread) {
327+
return ThreadId.get(thread);
328+
}
329+
316330
/**
317331
* Returns an array contain a reference to all {@link Thread} instances in the JVM. Since the JVM is
318332
* not stopped, the returned array may miss threads or have no longer extant threads.
@@ -389,6 +403,52 @@ public static MaxDirectMemoryInfo getMaxDirectMemoryInfo() {
389403
return MaxDirectMemoryInfoHelper.INSTANCE;
390404
}
391405

406+
/**
407+
* Handles using the JVM-appropriate method to obtain the id for a {@code Thread}.
408+
*/
409+
private static class ThreadId {
410+
private static final MethodHandle THREAD_ID_METHOD;
411+
412+
static {
413+
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
414+
MethodType longNiladic = MethodType.methodType(long.class);
415+
MethodHandle threadId;
416+
try {
417+
// Thread.threadId is added in Java 19 as a replacement for Thread.getId
418+
threadId = lookup.findVirtual(Thread.class, "threadId", longNiladic);
419+
} catch (NoSuchMethodException | IllegalAccessException e) {
420+
try {
421+
// Thread.getId is deprecated in Java 19
422+
threadId = lookup.findVirtual(Thread.class, "getId", longNiladic);
423+
} catch (NoSuchMethodException | IllegalAccessException ex) {
424+
threadId = MethodHandles.constant(long.class, -1L);
425+
426+
System.err.format("Failed to create handle for Thread 'id' method; calls to %s.threadId will return -1", Diagnostics.class.getSimpleName());
427+
ex.addSuppressed(e);
428+
ex.printStackTrace(System.err);
429+
}
430+
}
431+
THREAD_ID_METHOD = threadId;
432+
}
433+
434+
/**
435+
* Gets the id of the specified {@code Thread}.
436+
* @param t the {@code Thread} for which the id is to be obtained
437+
* @return the {@code Thread} id; {@code -1} if there is an error obtaining the {@code Thread} id
438+
*/
439+
private static long get(Thread t) {
440+
try {
441+
return (long)THREAD_ID_METHOD.invoke(t);
442+
} catch (Error e) {
443+
throw e;
444+
} catch (Throwable throwable) {
445+
System.err.format("Failed obtain thread id from %s; returning -1", THREAD_ID_METHOD);
446+
throwable.printStackTrace(System.err);
447+
return -1L;
448+
}
449+
}
450+
}
451+
392452
/**
393453
* Determines the process identifier of the current process.
394454
*/

test-tools/src/test/java/org/terracotta/utilities/test/DiagnosticsTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import java.util.concurrent.BrokenBarrierException;
3030
import java.util.concurrent.CyclicBarrier;
3131
import java.util.function.Consumer;
32+
import java.util.regex.Matcher;
33+
import java.util.regex.Pattern;
3234

3335
import static java.util.stream.Collectors.toList;
3436
import static org.hamcrest.Matchers.allOf;
@@ -97,6 +99,33 @@ public void testGetPid() {
9799
assertThat(intPid, is((int)pid));
98100
}
99101

102+
@Test
103+
public void testThreadId() throws Exception {
104+
Thread currentThread = Thread.currentThread();
105+
long threadId = Diagnostics.threadId(currentThread);
106+
assertThat(threadId, is(not(-1L)));
107+
108+
int javaVersion = 0;
109+
Matcher matcher = Pattern.compile("(?<first>\\d+)\\.(?<second>\\d+)\\..*").matcher(System.getProperty("java.version"));
110+
if (matcher.matches()) {
111+
if (matcher.group("first").equals("1")) {
112+
javaVersion = Integer.parseInt(matcher.group("second"));
113+
} else {
114+
javaVersion = Integer.parseInt(matcher.group("first"));
115+
}
116+
long id;
117+
if (javaVersion >= 19) {
118+
// At Java 19, Thread.threadId is added and Thread.getId is deprecated
119+
id = (long)Thread.class.getMethod("threadId").invoke(currentThread);
120+
} else {
121+
id = (long)Thread.class.getMethod("getId").invoke(currentThread);
122+
}
123+
assertThat(threadId, is(id));
124+
} else {
125+
fail("Unrecognized 'java.version'=" + System.getProperty("java.version"));
126+
}
127+
}
128+
100129
private List<String> perform(Consumer<PrintStream> task) {
101130
try {
102131
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(4096);

tools/src/main/java/org/terracotta/utilities/exec/Shell.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import java.io.BufferedReader;
2222
import java.io.IOException;
2323
import java.io.InputStreamReader;
24+
import java.io.InvalidObjectException;
25+
import java.io.ObjectInputStream;
26+
import java.io.Serializable;
2427
import java.nio.charset.Charset;
2528
import java.util.ArrayList;
2629
import java.util.Arrays;
@@ -86,13 +89,16 @@ public static Result execute(Charset consoleEncoding, String... command) throws
8689
/**
8790
* The {@link #execute(Charset, String...)} command execution result.
8891
*/
89-
public static final class Result implements Iterable<String> {
90-
private final List<String> commandLines;
92+
// Java 21 warns of non-Seriazable fields in a Serialization object
93+
public static final class Result implements Iterable<String>, Serializable {
94+
private static final long serialVersionUID = -5555911693879268481L;
95+
96+
private final transient List<String> commandLines;
9197
private final int exitCode;
9298

9399
private Result(int exitCode, List<String> commandLines) {
94100
this.exitCode = exitCode;
95-
this.commandLines = Collections.unmodifiableList(commandLines);
101+
this.commandLines = Collections.unmodifiableList(new ArrayList<>(commandLines));
96102
}
97103

98104
/**
@@ -119,6 +125,32 @@ public List<String> lines() {
119125
public Iterator<String> iterator() {
120126
return commandLines.iterator();
121127
}
128+
129+
private void readObject(ObjectInputStream s) throws InvalidObjectException {
130+
throw new InvalidObjectException("SerializationProxy expected");
131+
}
132+
133+
private Object writeReplace() {
134+
return new SerializationProxy(this);
135+
}
136+
137+
/**
138+
* Internal proxy for serialization of {@link Result} instances.
139+
*/
140+
private static final class SerializationProxy implements Serializable {
141+
private static final long serialVersionUID = -8686283741098525116L;
142+
private final int exitCode;
143+
private final ArrayList<String> commandLines;
144+
145+
SerializationProxy(Result result) {
146+
this.exitCode = result.exitCode;
147+
this.commandLines = new ArrayList<>(result.commandLines);
148+
}
149+
150+
private Object readResolve() {
151+
return new Result(exitCode, commandLines);
152+
}
153+
}
122154
}
123155

124156
/**

0 commit comments

Comments
 (0)