Add support for JDK25 - #5011
Open
JeremiahDJordan wants to merge 26 commits into
Open
Conversation
Add 25 to java.supported and provide conf/jvm25-server.options and conf/jvm25-clients.options (derived from the jvm21 baseline). The launch scripts (bin/cassandra.in.sh, tools/bin/cassandra.in.sh, redhat/cassandra.in.sh) select these files when running on JDK 25. The JDK 25 argument sets drop flags that no longer exist on that release (-XX:+ZGenerational, removed once ZGC became generational-only; and -Djdk.reflect.useDirectMethodHandle=false, removed in JDK 22), add --sun-misc-unsafe-memory-access=allow to silence the sun.misc.Unsafe deprecation warning, and add --enable-native-access=ALL-UNNAMED to grant native access to class-path code (e.g. netty's System::loadLibrary) so the JDK does not print "restricted method called" warnings to stderr (JEP 472), which future JDKs will otherwise block. -Djava.security.manager=allow is intentionally not set because installing a SecurityManager is no longer supported on JDK 25. JDK 23+ no longer runs annotation processors discovered on the class path unless annotation processing is explicitly requested, so build with -proc:full on JDK 21 and 25 to keep generating META-INF/hotspot_compiler. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
…agents Update the dependencies that gate JDK 25 support: - ASM 9.5 -> 9.8 (reads the class file version emitted by JDK 25). - Byte Buddy 1.17.8 -> 1.18.10, declared as a direct dependency so the managed version (which natively supports JDK 25) is used instead of an older one pulled in transitively; required by Mockito's inline mock maker. - Mockito 5.12.0 -> 5.23.0 and drop the separate mockito-inline artifact (the inline maker is the default in Mockito 5). - JaCoCo 0.8.8 -> 0.8.15 and java-allocation-instrumenter 3.1.0 -> 3.3.5. The allocation-instrumenter upgrade no longer re-exports a shaded Guava, which surfaced a handful of source files that had accidentally imported Guava through com.google.monitoring.runtime.instrumentation.common.*; point them at the real com.google.common.* classes. Refresh the IDE project classpath jar names. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
The AmazonCorrettoCryptoProvider native artifact is platform-specific, so the Maven classifier is chosen by the build/test host's OS and CPU. Replace the two Linux-only profiles with four mutually exclusive (os, arch) profiles that also cover macOS (osx-x86_64 and osx-aarch_64), using the "mac"/"!mac" os family to separate macOS from Linux since both report aarch64/x86_64. Bump the provider from 2.2.0 to 2.5.0, whose macOS classifiers let the crypto tests run on macOS developer machines (Cassandra itself ships on Linux). patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
User-defined functions run untrusted Java in the server process and were confined with a thread-based SecurityManager. A SecurityManager can no longer be installed on recent JDKs, so add a SecurityManager-free sandbox and select between the two mechanisms at runtime. The new sandbox rejects dangerous code at CREATE FUNCTION time with a bytecode verifier deny-list -- blocking System.exit/Runtime.halt, process execution, file and network I/O, reflection and method-handle escapes, JVM-internal and Unsafe APIs, and system-property reads including aliases such as Integer.getInteger -- backed by a filtering UDF class loader and the existing asynchronous execution watchdog, so unsafe code can never be linked or loaded. cassandra.udf.security_mechanism (auto|securitymanager|sandbox) chooses the mechanism; auto installs a SecurityManager where that is still possible and uses the sandbox otherwise. allow_extra_insecure_udfs continues to relax the restrictions where an operator has opted in. See cql3/functions/UDFSecurity.md for the sandbox design and security/SecurityManagerReplacement.md for the broader overview of how the SecurityManager was replaced. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
Tests that assert a tool or node calls System.exit installed a SecurityManager (PreventSystemExit) that turned the exit into a catchable exception. That mechanism cannot be installed on recent JDKs, so replace it with a Byteman rule that rewrites Runtime.exit/Runtime.halt to throw instead. SystemExitManager installs the rule against an already-running Byteman agent and reference-counts a process-global block; BytemanAgentSupport centralizes the agent lifecycle (fixed loopback host and a single dynamically chosen free port reused for the life of the JVM) and is shared with the Injections framework. ToolRunner, ClusterUtils, Instance, AbstractCluster, SSTableIdGenerationTest and the isolated LoaderOptionsTest are converted to the new interceptor, and PreventSystemExit is removed. Tool output capture is cleaned up so the Byteman rule installation and jamm's startup warning no longer leak into captured stdout/stderr. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
…wer JDKs JMX authorization and audit looked up the authenticated principal with Subject.getSubject(AccessControlContext) and ran work under Subject.doAs, both tied to the access-control machinery that is removed along with the SecurityManager. Add JMXSubjects, which uses Subject.current()/Subject.callAs on JDKs where the legacy methods no longer function and falls back to getSubject/doAs where they do, and route AuthorizationProxy and AuditLogManager through it. The choice is a runtime capability check so the code still compiles against JDK 11. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
Clearing a field's modifiers bit to write a final field through reflection stopped working in JDK 22. Replace those writes with sun.misc.Unsafe-based assignment in ReflectionUtils and FieldUtil, and update the call sites that reset final fields (Verb, Message, and the simulator's ClusterSimulation) to use the new helpers. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
JDK 25 disables legacy TLS protocols (TLSv1, TLSv1.1) and a number of older cipher suites by default. The internode, native-transport and sstableloader encryption tests hard-coded protocol and cipher expectations that no longer hold there. Teach AbstractEncryptionOptionsImpl to compute which protocols and ciphers are enabled by default on the running JDK and assert against that, so the tests pass on every supported JDK. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
The simulator rewrites bootstrap classes to make execution deterministic, which is sensitive to JDK internals. Update the transformer for JDK 25: pin a non-zero ThreadLocalRandom probe so the rewritten ForkJoinPool does not livelock, and hash only simulated enums by ordinal while keeping the real identity hash for JDK enums (with the helper added to InterceptorOfSystemMethods). Remap nestmate attributes in ShadowingTransformer so shadowed JDK classes such as ConcurrentHashMap's TreeBin retain a consistent nest host, and let RunStartDefiner initialize cleanly when no run id is present. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
Add a cassandra.simulator.seed property and route the simulator test entry points (SimulationTestBase, HarrySimulatorTest, SemaphoreTest, SingleNodeSingleTableASTTest and SimulationRunner) through it, so an entire run can be reproduced from a single base seed. Each call site falls back to its previous default when the property is unset, leaving existing behaviour unchanged. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
Allow building and packaging Cassandra on JDK 25: add it to the Debian and RPM build dependencies and install it in the build/test Docker images. Where the distribution's package repositories do not yet provide OpenJDK 25 it is installed from a checksum-verified GA tarball download (both x86_64 and aarch64). patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
new java.io.File("") reports as non-existent on older JDKs but resolves to the
current working directory on JDK 25 (exists(), isDirectory() and lastModified()
all reflect user.dir). Cassandra's File(String) maps the empty string to an
always-non-existent path, so the java.io.File equivalence harness no longer
matches for the empty path. Assert Cassandra File("")'s documented contract
directly instead, keeping the case covered and stable on every supported JDK.
patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
The SHA256 check read field two of sha256sum's output, but the digest and the filename are separated by two spaces, so field two is empty and the following grep matched any input -- the check could never fail. Cut field one so the actual digest is compared. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
Accord's build needs Gradle 9 to read JDK 25 bytecode (class file v69) when compiling its precompiled convention plugins, but Gradle 9 requires JDK 17+, while building on JDK 11 still needs Gradle 8. Select the Gradle distribution by the building JDK: keep the committed Gradle 8 wrapper for JDK 11 and repoint it at Gradle 9.6 for JDK 17+ for the duration of the build, restoring the committed wrapper afterwards. accord-maelstrom references the accord-core source sets directly so it builds under Gradle 9 (accord submodule bumped to a061c88). patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
The docker dtest runner installs ccm from the dtest requirements.txt, so local ccm changes cannot be exercised without first publishing them. Add an optional cassandra_ccm_dir, mirroring cassandra_dtest_dir: when set it is mounted into the container and pip-installed over the requirements.txt ccm, so a local ccm working copy can be validated end-to-end by the python dtests. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
The Accord submodule is pinned to a commit that adds Gradle 9 compatibility (referencing the accord-core source sets directly) which is not yet in apache/cassandra-accord, so a fresh "git submodule update --init" cannot fetch it from the upstream URL. Point the submodule at the JDK 25 Accord fork/branch so CI can check it out. Revert to apache/cassandra-accord once the Accord change is merged upstream. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
JDK 25 disables the legacy TLSv1/TLSv1.1 protocols and the TLS_RSA_WITH_AES_128_CBC_SHA cipher by default (jdk.tls.disabledAlgorithms). Tests that pinned these only incidentally -- to exercise config/cache-key equality, SSL context construction, or internode framing rather than the specific legacy version/cipher -- are updated to a protocol and cipher that stay enabled on JDK 11/17/21/25: TLSv1.2 and TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (the modern suite already used elsewhere as the JDK-25 fallback). Tests that specifically assert the legacy protocols/ciphers are rejected, or that gate them behind isProtocolEnabledByDefault()/isCipherEnabledByDefault(), are left unchanged so they keep verifying that behaviour. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
Document JDK 25 as a supported build and runtime target under 7.0, mirroring the JDK 21 entry: generational ZGC is the recommended collector (the non-generational ZGC mode was removed from the JDK), and installing a SecurityManager is no longer supported on JDK 25 (JEP 411), with user-defined function sandboxing moved to a SecurityManager-free mechanism. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
jamm 0.4.0 reads the UseEmptySlotsInSupers HotSpot VM option to choose its memory sizing strategy. JDK 25 removed that option, which jamm mis-detects as the option being disabled: it selects the DoesNotUseEmptySlotInSuper strategy (incorrect sizing) and prints a warning to stdout that corrupts tool output parsed by tests and users (e.g. sstabledump JSON, nodetool). The published 0.4.0 is the latest release and no property or JVM flag disables this. Build jamm from the modules/jamm submodule (a fork that treats the absent option as enabled on Java 15+, so Java 25 uses the correct strategy and prints no warning) and install it to the local Maven repository as org.apache.cassandra:cassandra-jamm, mirroring how Accord is built. The parent pom depends on cassandra-jamm so the corrected build is resolved from the local build rather than the upstream com.github.jbellis:jamm release; the resolved jar is not overwritten. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
…ks on JDK 25 JDK 25 rewrote GaloisCounterMode.overlapDetection to walk a direct buffer's attachment chain casting each link to java.nio.Buffer (NIO_ACCESS.getBufferAddress((Buffer) att)); JDK <= 24 cast to sun.nio.ch.DirectBuffer and called address(). BufferPool stashes the owning Chunk (or a Ref.DirectBufferRef when -Dcassandra.debugrefcount is enabled) in the pooled buffer's DirectByteBuffer.att field, and neither is a java.nio.Buffer. Because AES-GCM is the default TLS 1.2/1.3 cipher, every encrypted internode and native connection failed on JDK 25 with a ClassCastException during the handshake's GCM decrypt. On JDK 25+ stash a per-chunk java.nio.Buffer marker (a hollow direct buffer with a null attachment and an address of the chunk base) in each sliced buffer's att instead, and recover the owning Chunk from CHUNK_BY_MARKER keyed by the marker's object identity. The null attachment stops overlapDetection's walk at the chunk boundary rather than chaining up to the 8MB macro root, keeping the window where it defensively heap-copies dst down to a single 128KB chunk. Identity keying is required because nested chunks share base addresses (a macro chunk and its first normal child both start at offset 0), so an address key would recover the wrong chunk. CHUNK_BY_MARKER is a weak-keyed identity map (Guava MapMaker().weakKeys()); entries are dropped in Chunk.dropAttachmentMarker() from both unsafeFree() and when a tiny chunk is recycled back to its parent. Tiny chunks - and their markers - are created and retired millions of times per second under load, and a strong-reference map (e.g. NonBlockingIdentityHashMap) retains removed keys in its internal table until a resize, leaking the marker buffers and OOMing the heap; weak keys reclaim a marker's entry once it is unreferenced even if an explicit drop is missed. JDK <= 24 keeps the original zero-overhead direct Chunk attachment (no map, no per-free lookup, DirectBufferRef leak tracking preserved); the map is gated on Runtime.version() and never populated there, since the bug does not exist on those JDKs (verified the cast lands in JDK 25, not 22/23/24). Adds a BufferPool + AES/GCM/NoPadding direct-ByteBuffer round-trip regression test, with both buffers from the same macro slab, that fails with the ClassCastException on the old attachment under JDK 25; the marker-map leak is covered by the LongBufferPoolTest burn test. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
…wn on a dedicated executor testDisallowsNewRequests submitted the post-shutdown query via CompletableFuture.supplyAsync, which runs on the common ForkJoinPool. Common-pool parallelism is 1 (the in-JVM dtest JVM sees two processors) and its single worker is already blocked in the disablebinary call, which drains the in-flight queries for about a second. On JDK 17 the pool promptly starts a compensating worker, so the query runs immediately and the stopped native transport rejects it with OverloadedException. The JDK 25 ForkJoinPool rework does not start a compensating worker, so the task is starved until disablebinary completes about a second later, after the server has reset the connection and the driver marked the host down, and the query fails client-side with NoHostAvailableException instead. The behaviour is deterministic and opposite per JDK (JDK 17 OverloadedException; JDK 25 NoHostAvailableException), not a flake. Run the query on a dedicated single-thread executor instead of the common pool so it executes immediately, inside the window where the stopped transport returns OverloadedException, restoring the strict assertion on all JDKs. Validated on JDK 17 and JDK 25. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
The UDF sandbox allows the java/lang/ class-load prefix, so a UDF can name java.lang.ClassLoader, and configureBaseDisallowed denied only an enumerated set of its methods. It omitted getPlatformClassLoader(), getParent() and resources(String), each of which hands back a live ClassLoader or resource stream. No present-day break-out results (the capabilities reachable from an obtained loader are independently blocked), but a name-based denylist that must enumerate every dangerous method is fragile: a future JDK adding a loader/resource-yielding method to ClassLoader would silently widen the sandbox with no failing test. Deny java/lang/ClassLoader as a class instead. The verifier already rejects any call whose owner is a disallowed class, so this covers the three missing methods and any added later, without enumerating them. The generated UDF wrapper never references ClassLoader, so no legitimate UDF is affected; the existing per-method entries are kept as documentation of the surface. The denial lives in configureBaseDisallowed, so it applies under both the SecurityManager and SecurityManager-free mechanisms. Add two regression tests to UFSecurityTest: per-method rejection of getPlatformClassLoader/getParent (verifier) and resources (rejected at compilation, since its Stream<URL> return type is not resolvable in the sandbox), plus a reflective guard that asserts every public loader/resource-yielding ClassLoader method is un-creatable, so a future JDK addition or a downgrade of the denial fails CI. Validated on JDK 25 (6/0/0) and JDK 17 (6 run, 1 expected skip). patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
…Settings on JDK 25 testSystemSettings connects with the JDK's stock SslRMIClientSocketFactory, which on JDK 25 performs TLS endpoint identity verification. The default test keystore (cassandra_ssl_test.keystore, CN=Apache Cassandra) carries no SubjectAltName, so the handshake fails with SSLHandshakeException: (certificate_unknown) No subject alternative names present. The other tests in this class connect through a custom client socket factory that does not verify endpoint identity, so they are unaffected. Point testSystemSettings at cassandra_ssl_test_endpoint_verify.keystore (CN=127.0.0.1 with an IPAddress:127.0.0.1 SAN), which the shared test truststore already trusts (alias mykey). Validated on JDK 25 and JDK 17 (1/0/0 each). patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
…ForkJoinPool A codebase audit for JDK 25's reworked ForkJoinPool (which no longer eagerly starts a compensating worker when a common-pool worker blocks at low parallelism) found four tests that submit blocking work - CQL queries and nodetool calls - to the common pool via IntStream.parallel() or CompletableFuture.supplyAsync/runAsync with no explicit executor. Under JDK 25 these can serialize or starve, the same failure class as DisableBinaryTest. Route each through a dedicated executor: VectorSiftSmallTest (4 parallel insert/recall loops -> a fixed thread pool helper), and MixedModeRepairTest, RepairCoordinatorNeighbourDown and DecommissionAvoidTimeouts (supplyAsync/runAsync -> a single-thread executor, shut down after the task is awaited). Production code was confirmed unaffected (it uses Cassandra's own ExecutorPlus framework, not the common pool). VectorSiftSmallTest validated on JDK 17 and JDK 25 (2/0/0 each); the three dtests compile clean. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
… JDK 16+ Ref.getFieldValue falls back to Unsafe.objectFieldOffset when a field cannot be made reflectively accessible (module-protected). On JDK 16+ that call throws UnsupportedOperationException for record components, and JDK 25 enforces this hard. The resulting UnaccessibleFieldException (a RuntimeException) was not caught by the field walk in InProgressVisit.nextChild, so it propagated to the outermost handler in Visitor.run and aborted the entire leak-detection pass: the first JDK record reachable from any tracked Tidy graph (e.g. java.security.SecureClassLoader$CodeSourceKey) silently disabled leak checking for every remaining GlobalState. Records are pervasive in the JDK since 16, so on JDK 25 the strong-reference leak detector was largely defeated. Catch UnaccessibleFieldException in nextChild, log it throttled via NoSpamLogger, and skip the single unreadable field (record, hidden class, or module-protected) instead of aborting the whole scan; the field index is already advanced, so the walk safely continues with the rest of the graph. Also drop a redundant second getFieldValue call on the same field. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
The [ \$USING_ZGC -eq 0] test was missing the space before the closing bracket. Follow-on fix to be squashed into the JDK 25 branch. patch by Jeremiah Jordan; reviewed by <Reviewer Here> for CASSANDRA-21171
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CASSANDRA-21171: Add JDK 25 support
Adds JDK 25 as a supported build and runtime target alongside JDK 11/17/21.
Build & dependencies
jvm25-server.options/jvm25-clients.options;java.supportedincludes 25mockito-inline), JaCoCo, allocation-instrumenter, and the Amazon Corretto Crypto Provider (all platforms) to versions that read/run JDK 25 bytecode.SecurityManager removal (JEP 411, finalized in JDK 24)
java.lang.ClassLoaderwholesale in the UDF byte-code verifier.System.exitin tests via Byteman; resolve JMX authorization subjects viaSubject.current()/callAs; overwritestatic finalfields viaUnsafeinstead of reflection.JDK 25 runtime correctness
GaloisCounterMode.overlapDetectionto walk a direct buffer's attachment chain and cast each link tojava.nio.Buffer.BufferPoolstashed aChunkthere, so every encrypted connection threwClassCastException. Fixed by attaching a per-chunk zero-capacityBuffermarker and recovering the owningChunkfrom a weak-keyed side-map (gated on JDK ≥ 25).Ref's field walk now skips record components it can't read viaUnsafe(which throws on JDK 25) instead of aborting the scan.Tests & CI
ForkJoinPoolon JDK 25 doesn't spawn a compensating worker under low parallelism; blocking test actions (DisableBinaryTestand several dtests) now run on dedicated executors for determinism.FileTest's empty-path case stable across the JDK 25 pathname change; support and seed-pin the simulator on JDK 25.Companion branches (same ticket)