fix(core): compatibility with JDK 26 #406
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
| name: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - main | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| # JDK 8 is the source of truth: the client ships as a Java 8 artifact | |
| # (io.questdb:questdb-client) and is released from JDK 8, so on JDK 8 it must | |
| # compile, the full test suite must pass, and the javadoc jar must build | |
| # (-P javadoc attaches it at the package phase). The native libraries are no | |
| # longer committed, so this job compiles libquestdb.so from source (hence the | |
| # zstd submodule + cmake/nasm/build-essential toolchain) before the tests run. | |
| build-jdk8: | |
| name: Build, test & javadoc (JDK 8) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| with: | |
| # zstd is required to compile the native library. | |
| submodules: recursive | |
| - name: Set up JDK 11 (MRJAR versioned classes) | |
| # Exports JAVA_HOME_11_X64. The JDK 8 build compiles src/main/java11 with | |
| # it into META-INF/versions/11 (see the antrun execution in core/pom.xml); | |
| # JDK 8 javac cannot compile that source root. | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "11" | |
| - name: Set up JDK 8 | |
| # Last setup-java call wins JAVA_HOME: the build itself runs on JDK 8. | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "8" | |
| cache: maven | |
| - name: Install native build toolchain | |
| run: sudo apt-get update && sudo apt-get install -y cmake nasm build-essential | |
| - name: Build native libquestdb.so | |
| # JAVA_HOME points at the JDK 8 above, so the lib is compiled against the | |
| # Java 8 JNI headers -- the artifact's Java floor. Copy it into src | |
| # resources (not target/) so it survives the `mvn clean` in the next step | |
| # and gets packaged + loaded via the production bin/<platform> path. | |
| # NOTE: this builds on ubuntu-latest for FUNCTIONAL testing only; the | |
| # library's glibc runtime floor is validated separately by the | |
| # `glibc-floor` job, which rebuilds in the release low-glibc container. | |
| run: | | |
| cd core | |
| cmake -DCMAKE_BUILD_TYPE=Release -B cmake-build-release -S. | |
| cmake --build cmake-build-release --config Release | |
| test -f target/classes/io/questdb/client/bin-local/libquestdb.so | |
| mkdir -p src/main/resources/io/questdb/client/bin/linux-x86-64 | |
| cp target/classes/io/questdb/client/bin-local/libquestdb.so \ | |
| src/main/resources/io/questdb/client/bin/linux-x86-64/libquestdb.so | |
| - name: Compile, test, and build javadoc | |
| env: | |
| JAVA11_HOME: ${{ env.JAVA_HOME_11_X64 }} | |
| run: mvn -B -ntp -P javadoc clean install | |
| - name: Upload client jar for the cross-JDK smoke job | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: questdb-client-jar | |
| path: | | |
| core/target/questdb-client-*.jar | |
| !core/target/*-tests.jar | |
| !core/target/*-javadoc.jar | |
| !core/target/*-sources.jar | |
| if-no-files-found: error | |
| # The 1.3.5-1.3.7 releases shipped a JDK 8-built jar whose FdBig class links | |
| # sun.misc.FDBigInteger (gone since Java 9) and whose module name degraded to | |
| # the filename-derived "questdb.client". Both are only observable in the | |
| # PACKAGED jar on a modern JDK, so take the jar built by build-jdk8 and prove | |
| # on JDK 25 that (a) the automatic module name is io.questdb.client and | |
| # (b) slow-path double formatting resolves the META-INF/versions/11 bridge. | |
| # Runs the JDK 8-built jar (the shipped artifact) on every newer JDK the | |
| # client must work on. The double formatter reaches into jdk.internal.math | |
| # via the META-INF/versions/11 bridge, and a JDK-internal change there is | |
| # only visible when the packaged jar is EXECUTED on that JDK: issue #96 | |
| # (JDK 26 made FDBigInteger package-private) passed every JDK 8/11/25 check | |
| # and broke only at runtime on 26; JDK 27 compact object headers (JEP 450, | |
| # default-on) then shifted the AccessibleObject.override offset the bridge | |
| # relies on -- both are exercised here. The 27-ea entry is a forward canary: | |
| # it should be green, and does not fail the workflow because EA is a moving | |
| # target -- a red run means the next JDK needs attention. | |
| mrjar-smoke: | |
| name: MRJAR smoke (JDK 8 jar on JDK ${{ matrix.java }}) | |
| needs: build-jdk8 | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| continue-on-error: ${{ matrix.experimental }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| java: ["25", "26"] | |
| experimental: [false] | |
| include: | |
| - java: "27-ea" | |
| experimental: true | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK ${{ matrix.java }} | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: ${{ matrix.java }} | |
| - name: Download JDK 8-built client jar | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: questdb-client-jar | |
| path: client-jar | |
| - name: Check module name and run double-formatting smoke | |
| run: | | |
| jar_file=(client-jar/questdb-client-*.jar) | |
| jar_file="${jar_file[0]}" | |
| jar --describe-module --file "$jar_file" | tee module.txt | |
| grep -q '^io\.questdb\.client@' module.txt | |
| javac -cp "$jar_file" -d smoke-classes \ | |
| core/src/test/java/io/questdb/client/test/std/DoubleFormatSmoke.java | |
| # The FdBig double formatter reaches jdk.internal.math.FDBigInteger by | |
| # setting AccessibleObject.override at an offset Unsafe derives from the | |
| # object header size. Run the smoke under each object-header layout so a | |
| # regression to a hard-coded offset fails a BLOCKING leg (25, 26), not | |
| # only the non-blocking 27-ea canary. Compact object headers (JEP 450) | |
| # are a product flag on JDK 25/26 and default-on from 27; the | |
| # +IgnoreUnrecognizedVMOptions guard keeps the flags harmless on any JDK. | |
| run_smoke() { # <label> <jvm flags...> | |
| local label="$1"; shift | |
| echo "::group::DoubleFormatSmoke [$label]" | |
| java "$@" -cp "$jar_file:smoke-classes" io.questdb.client.test.std.DoubleFormatSmoke | |
| echo "::endgroup::" | |
| } | |
| run_smoke "default" -XX:+IgnoreUnrecognizedVMOptions | |
| run_smoke "compact-headers" -XX:+IgnoreUnrecognizedVMOptions -XX:+UseCompactObjectHeaders | |
| run_smoke "uncompressed-oops" -XX:+IgnoreUnrecognizedVMOptions -XX:-UseCompressedOops -XX:-UseCompressedClassPointers | |
| # The client is also consumed as a submodule of the main questdb repo, which | |
| # builds on JDK 25, and contributors build on the newest GA JDK (issue #96 | |
| # was "does not compile on JDK 26"). Guard against compile breakage on both | |
| # (main + test sources, both modules) and confirm the javadoc jar builds too | |
| # (-P javadoc attaches it at the package phase). Do NOT run the tests -- the | |
| # parent repo runs them against a real server. | |
| compile-modern-jdk: | |
| name: Compile & javadoc smoke (JDK ${{ matrix.java }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| java: ["25", "26"] | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK ${{ matrix.java }} | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: ${{ matrix.java }} | |
| cache: maven | |
| - name: Compile (main + test) and build javadoc (no tests run) | |
| run: mvn -B -ntp -P javadoc -DskipTests clean package | |
| # GLIBC floor guard. The native libraries are built at release time in | |
| # low-glibc manylinux containers (see maven_central_release.yml) and are NOT | |
| # committed, so a floor regression is invisible to the functional test job | |
| # above (it builds on ubuntu-latest, whose glibc is new enough to load almost | |
| # anything). This job rebuilds the linux libraries in the SAME low-glibc | |
| # environment as release and asserts the runtime floor with objdump, so a | |
| # change that raises the floor (e.g. a new stat/fstat call pulling in | |
| # stat@GLIBC_2.33 on a modern build host) fails the PR instead of silently | |
| # shipping a library that cannot load on older distros. | |
| # | |
| # * linux-x86-64 -> GLIBC_2.14 (the intended floor: memcpy@GLIBC_2.14). | |
| # * linux-aarch64 -> GLIBC_2.17 (the lowest floor glibc offers on aarch64). | |
| # | |
| # Uses manylinux_2_28 for both arches (stock Node 24, no glibc-2.17 shadow | |
| # hack). The x86-64 floor is identical in manylinux2014 (2.17) and | |
| # manylinux_2_28 (2.28) -- both resolve stat/fstat to the inline | |
| # __xstat/__fxstat@GLIBC_2.2.5 wrappers -- so this validates the real shipped | |
| # floor without the heavier manylinux2014 release toolchain. | |
| glibc-floor: | |
| name: GLIBC floor guard (${{ matrix.platform }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux-x86-64 | |
| os: ubuntu-latest | |
| image: quay.io/pypa/manylinux_2_28_x86_64 | |
| jdk_arch: x64 | |
| floor: "2.14" | |
| cmake_args: "" | |
| build_dir: cmake-build-release | |
| - platform: linux-aarch64 | |
| os: ubuntu-22.04-arm | |
| image: quay.io/pypa/manylinux_2_28_aarch64 | |
| jdk_arch: aarch64 | |
| floor: "2.17" | |
| cmake_args: "-DCMAKE_TOOLCHAIN_FILE=./src/main/c/toolchains/linux-arm64.cmake" | |
| build_dir: cmake-build-release-arm64 | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| container: | |
| image: ${{ matrix.image }} | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| with: | |
| # zstd is required to compile the native library. | |
| submodules: recursive | |
| - name: Install tooling | |
| # binutils provides objdump for the floor check; nasm/zstd are build deps. | |
| run: | | |
| yum update -y | |
| yum install -y wget nasm zstd binutils | |
| - name: Install Temurin JDK 8 (for jni.h) | |
| # Build against the Java 8 JNI headers -- JDK 8 is the artifact's floor. | |
| # The JDK version does not affect the glibc floor; it only supplies jni.h. | |
| run: | | |
| wget -v --timeout=180 -O jdk8.tar.gz \ | |
| "https://api.adoptium.net/v3/binary/latest/8/ga/linux/${{ matrix.jdk_arch }}/jdk/hotspot/normal/eclipse" | |
| mkdir jdk8 | |
| tar xfz jdk8.tar.gz -C jdk8 --strip-components=1 | |
| echo "JAVA_HOME=$(pwd)/jdk8" >> "$GITHUB_ENV" | |
| - name: Build native libquestdb.so | |
| run: | | |
| cd core | |
| cmake ${{ matrix.cmake_args }} -DCMAKE_BUILD_TYPE=Release -B ${{ matrix.build_dir }} -S. | |
| cmake --build ${{ matrix.build_dir }} --config Release | |
| - name: Assert GLIBC floor | |
| run: | | |
| ./.github/scripts/check-glibc-floor.sh \ | |
| core/target/classes/io/questdb/client/bin-local/libquestdb.so \ | |
| "${{ matrix.floor }}" |