-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Summary
When running mvn test -e on OpenCMW with JDK 17 (Windows environment) I encountered a set of issues that caused test failures and build interruptions. I investigated the causes, applied minimal, defensive code fixes, and verified module tests pass locally. This issue documents the reproduction steps, observed errors, root causes, the fixes I applied (with code excerpts), verification results.
Environment
- OS: Windows 11 (cmd.exe default shell)
- JDK: OpenJDK 17 (example used in my environment)
- Maven: 3.x
- Repo path used locally: D:\study\opencmw-java
- Modules present: core, serialiser, server, server-rest, client, concepts, etc.
How to reproduce (local)
From repository root:
cd D:\study\opencmw-java
# Run full project tests (may take some time)
mvn test -e
To test focused modules (faster, for debugging):
# serialiser module only
mvn -pl serialiser test -e
# core module only
mvn -pl core test -e
# debug logging for core module
mvn -pl core test -e -X
Observed errors
I observed several distinct runtime/build problems while running tests under JDK 17:
- serialiser: static initializer attempted to touch jdk.internal.module.IllegalAccessLogger which caused initialization problems on some JVMs.
- Symptom: Exception in static init of classes in serialiser (related to Unsafe / IllegalAccessLogger).
- core: resource path returned by URL#getPath() during tests started with /D:/... (Windows), causing Paths.get(configFile) to throw:
- java.nio.file.InvalidPathException: Illegal char <:> at index 2: /D:/...
- core: attempts to obtain local host/IP using network internals triggered sun.nio.ch.Net initialization problems on some JVMs:
- Symptoms: java.lang.NoClassDefFoundError: Could not initialize class sun.nio.ch.Net and related ExceptionInInitializerError.
- This surfaced in tests that call OpenCmwConstants.getLocalHostName() or related network utilities.
- Module system warnings (not fatal but noisy): filename-based automodules such as jsoniter-0.9.23.jar show warnings about automatic modules.
Root cause analysis
A combination of JVM internals usage and platform-specific path formats caused failures:
- Code in FastByteBuffer statically accessed internal JVM symbols in a brittle way; this can fail on some JDK builds.
- Test resource handling used URL#getPath() without normalizing Windows-style returned paths that may include a leading slash before the drive letter.
- Local-host detection relied solely on a DatagramSocket method that, on some JDK implementations/environments, triggers sun.nio.ch.Net initialization issues; code was not resilient to that failure.
Fixes applied (minimal, defensive changes)
I made three small, targeted changes to make code more defensive across JVM variants and Windows paths. The changes are intentionally minimal so they can be reviewed and merged as a quick patch / hotfix.
1) FastByteBuffer (serialiser)
File:serialiser/src/main/java/io/opencmw/serialiser/spi/FastByteBuffer.java
Change summary:
- Obtain sun.misc.Unsafe first in a safe try/catch.
- Attempt to silence jdk.internal.module.IllegalAccessLogger only if it exists; ignore ClassNotFoundException / NoSuchFieldException rather than failing initialization.
Core excerpt:
private static final Unsafe unsafe;
static {
Unsafe tmpUnsafe = null;
try {
final Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
tmpUnsafe = (Unsafe) field.get(null);
// try to silence the IllegalAccessLogger when present (some JVMs)
try {
Class<?> cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field logger = cls.getDeclaredField("logger");
tmpUnsafe.putObjectVolatile(cls, tmpUnsafe.staticFieldOffset(logger), null);
} catch (ClassNotFoundException | NoSuchFieldException ignored) {
// not present on this JVM, continue normally
}
} catch (SecurityException | IllegalAccessException | NoSuchFieldException e) {
throw new SecurityException(e);
}
unsafe = tmpUnsafe;
}
Rationale:
- Prevents static initializer from failing if jdk.internal.module.IllegalAccessLogger is not present (varies across JVM distributions and versions).
2) SystemProperties config path normalization (core)
File:core/src/main/java/io/opencmw/utils/SystemProperties.java
Change summary:
- When loading config files, detect Windows "/D:/..." style paths and strip the leading slash before calling Paths.get(...).
Core excerpt:
final Properties configFileProperties = new Properties();
try {
// handle Windows resource paths that may look like '/D:/path/to/file' coming from URL#getPath()
String cfgPath = configFile;
if (cfgPath.length() > 2 && cfgPath.charAt(0) == '/' && Character.isLetter(cfgPath.charAt(1)) && cfgPath.charAt(2) == ':') {
cfgPath = cfgPath.substring(1);
}
try (InputStream input = Files.newInputStream(Paths.get(cfgPath))) {
configFileProperties.load(input);
}
} catch (final IOException e) {
if (!DEFAULT_CFG.equals(configFile)) {
throw new IllegalArgumentException("could not find file: '" + Paths.get(configFile) + "'", e);
}
// fallback behavior for missing default.cfg
}
Rationale:
- Fixes InvalidPathException on Windows when resource paths are used in tests.
3) OpenCmwConstants.getLocalHostName() defensive fallback (core)
File:core/src/main/java/io/opencmw/OpenCmwConstants.java
Change summary:
- Keep DatagramSocket method but catch any exception and fall back to InetAddress.getLocalHost().
- If that also fails, return "localhost" to make tests robust.
- Ensure all control paths return a string (fixing compilation complaint about missing return).
Core excerpt:
public static String getLocalHostName() {
String ip;
try (DatagramSocket socket = new DatagramSocket()) {
try {
socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
if (socket.getLocalAddress() == null) {
throw new UnknownHostException("bogus exception can be ignored");
}
ip = socket.getLocalAddress().getHostAddress();
if (ip != null) {
return ip;
}
return "localhost";
} catch (final Exception e) {
// fallback: try InetAddress.getLocalHost()
try {
final InetAddress localhost = InetAddress.getLocalHost();
if (localhost != null && localhost.getHostAddress() != null) {
return localhost.getHostAddress();
}
} catch (final Exception ex) {
return "localhost";
}
}
} catch (final Exception e) {
return "localhost";
}
// defensive fallback
return "localhost";
}
Rationale:
- Avoids letting platform/JVM-specific network internals cause exceptions that make tests fail.
Verification / Test results (local)
After applying the changes locally:
- Serialiser module:
mvn -pl serialiser test -e
Result: Tests run: 128, Failures: 0, Errors: 0
- Core module:
mvn -pl core test -e
Result: Tests run: 150, Failures: 0, Errors: 0