Repository navigation
Conversation
…dll` linking and adding build documentation.
The previous regeneration used Autoconf 2.73 and rewrote ~1300 lines. Regenerating with Autoconf 2.72 (the generator of upstream's configure, which it reproduces byte for byte) limits the difference from upstream to the dynload default and the skipped link-time JNI checks.
…s paths Windows: replace four fixed 32768-wchar stack buffers with length-queried heap allocations. GetFullPathNameW returns the required size when the buffer is too small, and GetModuleFileNameW returns the buffer size when it truncates; both were previously accepted as success. A jvm.dll already mapped is now compared by volume serial and file ID (FileIdInfo, falling back to BY_HANDLE_FILE_INFORMATION), so 8.3 names, junctions, symbolic links and other spellings of the same file are accepted, while different files and unverifiable identities are still rejected. When the identity matches, djni takes a reference to the mapped module by address instead of loading the alias, so a second copy of jvm.dll cannot be mapped. Errors include the path and the system message. POSIX: compare st_dev/st_ino instead of realpath() strings, and dlopen a mapped JVM by its own name. Pass native-encoded paths to dlopen. Add djni_mapped()/RgetMappedJVM: reports a JVM library already mapped into the process and its number of created VMs (RTLD_NOLOAD or an unchanged module reference), without loading or initializing anything. Add compile-time checks of the JNI type sizes, which configure no longer tests because src/jni.h replaces the JDK headers. Reject empty paths.
Restore upstream behavior for an invalid explicit Java home: as rJava did on Windows (java.home option or JAVA_HOME) and macOS (JAVA_HOME), a value that is not an existing directory is ignored with the upstream warning and discovery falls back to the registry (Windows), /usr/libexec/java_home (macOS), then java on PATH. Selection still happens only at the first .jinit()/.jloadJVM(); an existing home without a JVM library is an error that leaves the process able to retry. Find the JVM library in known layouts first (lib/server, jre/lib/<arch>/ server for JDK 8 on Linux, jre/lib/server for JDK 8 on macOS, bin/server and jre/bin/server on Windows), then search recursively. If a JVM has already been created in the process, e.g. by a Java application embedding R through JRI, attach to it whatever Java home is configured. That is the only usable runtime, and the behavior of rJava linked against the JVM library. A library mapped without a created VM still rejects a different runtime.
test_rjava_win.R was a manual script requiring an edited JAVA_HOME, and it was shipped at the top level of the source tarball built by mkdist. The native Windows CI scenarios in tests-integration/ supersede it.
The guard skipped upstream's R CMD check matrix (Zulu 8 and 11 on five runner images including Intel macOS). It adds older-JDK and vendor coverage under upstream's own build path, so keep it running.
…arch Run a bounded matrix per platform: legacy layouts (Temurin 8/11; Zulu 8 on arm64 macOS), current LTS (Temurin 17/21, plus the downstream rJavaEnv packages), and other vendors (Corretto 11, Zulu 25). The build JDK is always Temurin 17. Every scenario starts a fresh R process with PATH reduced to system and R directories and JAVA_HOME set explicitly, so the runner's Java setup cannot mask discovery or dependency handling. New scenarios: bad and over-long paths; same-runtime selection of a preloaded library through symbolic links/junctions, 8.3 names (Windows) and alternative spellings; non-ASCII paths with spaces; the fallback order for an invalid Java home, with the expected home registered under HKCU on Windows; later native loads (zip, net, management, security, mscapi, AWT imaging) with loaded Windows modules logged; JRI started from R; and a Java host embedding R via JRI, where rJava must attach to the host JVM although JAVA_HOME names the other JDK. Compare src/jni.h with each JDK's jni.h (type sizes, invocation struct fields and every function table offset). On Windows, a native harness checks that the bin directory added for jvm.dll's dependencies is no longer searched after failed and successful loads, with a positive control.
djni defines wrappers named JNI_CreateJavaVM, JNI_GetCreatedJavaVMs and JNI_GetDefaultJavaVMInitArgs. On Linux, when a JVM library is already loaded with global symbols (the java launcher does this in a Java application embedding R through JRI), ELF interposition bound rJava's own calls to libjvm's exports instead: rJava saw a running VM, skipped selection, and .jinit() then failed with "No Java run-time is loaded". Give the wrappers hidden visibility so references bind within rJava.so. macOS two-level namespaces and Windows imports were not affected.
Pre-existing since JRI moved to src/jri in 1.0-15 (e28ebb6): configure.win still tested jri/configure.win, so Windows source installs silently built no JRI. Use the src/jri paths. Needed to test JRI embedding on Windows; independent of deferred loading.
Reproduces, inside an R process, the symbol interposition a Java host causes on Linux.
Correcting configure.win only exposes a second pre-existing failure: src/jri/Rdecl.h includes <Rinterface.h>, which R does not ship on Windows. Windows source installs keep building without JRI, as on upstream master. This reverts commit 0eacc04.
…+ runtime A helper compiled with R CMD SHLIB creates a JVM in the R process before rJava is used (with global symbols on POSIX), so attaching to a created JVM is tested on Windows too, where JRI is not built from source. On Windows, hide System32's VC++ runtime DLLs: jvm.dll and its startup libraries must load from the JDK alone. Record whether later loads that import another runtime DLL still work in that environment.
Rewrite tests-integration/README.md: selection order and file-identity rule; why selected libraries are never unloaded (a separate safety boundary); scoped Windows DLL search and the CI evidence that later loads of awt.dll/sunmscapi.dll need a VC++ runtime outside the JDK, as upstream, with two unimplemented options for maintainers; the test matrix; and what is not covered, including Windows JRI (two pre-existing build defects). Also run the later-load scenario with JDK B, so all six runtimes of the matrix are exercised.
A JVM library mapped under another basename (renamed copy, hard link) was invisible to djni_mapped() and the conflict check, so djni_load() mapped a second runtime. djni now lists every loaded image (dyld, dl_iterate_phdr, EnumProcessModules) without loading anything, and treats an image as a JVM if it has a JVM library name or itself defines JNI_CreateJavaVM, JNI_GetCreatedJavaVMs and JVM_CurrentTimeMillis. Inherited definitions, forwarded Windows exports, rJava's own wrappers and the macOS libjli (JNI shims only) do not count. Several mapped JVM images reject selection. On Windows a JVM mapped under another module name is rejected, as the JDK's DLLs import jvm.dll by name. If images cannot be listed, selection fails and RgetMappedJVM signals an error. Tests: renamed copy/hardlink/symlink, hosted renamed and a jli decoy; CI checks each runtime's exports. Pins rJavaEnv at 693b87c.
…t name The file at the name the loader recorded may have been deleted or replaced since the library was mapped, e.g. by a JDK upgrade. Comparing the requested file with it accepted B for a mapped A and reused A's handle. Identify the mapped file from the mapping itself: /proc/self/maps on Linux, the region vnode from proc_pidinfo on macOS, GetMappedFileNameW on Windows. Selecting the reported name of a library with a created VM still attaches to it. Add deleted, replaced and hosted replaced scenarios with two JDKs.
… path .jloadJVM(path) accepted the name the loader recorded for a JVM image that had created a VM without comparing files, so a public call with that name succeeded after another runtime replaced the file there. Explicit paths are now always compared with the mapped file. .jinit() attaches through a new internal RattachJVM, which takes no path: it requires exactly one mapped JVM image with a created VM and selects it through the reference djni holds.
…name On musl, the libjvm.so dependency of libjava, libjimage and the other JDK libraries is resolved through LD_LIBRARY_PATH as set at process start, not matched to the loaded library's SONAME as glibc does. Selecting a runtime other than the one on that path mapped a second JVM library while starting the VM. Check by name with RTLD_NOLOAD, before creating a VM.
…artup path Run replaced deleted and replaced replaced with A's and with B's server directory on the startup library path, and hosted replaced with A's. After each refusal, check that nothing from B's home was mapped.
musl takes a handle to a mapped image only by opening its recorded name again, so a JVM library mapped under a name no JVM uses was dropped from discovery once that file was deleted or replaced, and selecting another runtime then mapped it next to the first (reproduced on Alpine: with B's server directory on the startup path, .jloadJVM(B) succeeded). Read JVM_CurrentTimeMillis, JNI_CreateJavaVM and JNI_GetCreatedJavaVMs from the image's dynamic symbol table in memory instead, bounded by its readable segments; an unreadable table fails discovery. Take references only through names that resolve to the image itself, checked with dlinfo(RTLD_DI_LINKMAP): the recorded name, libjvm.so, or for a selection the path naming the mapped file; refuse selection or attachment without one. Also drop trailing whitespace in djni.c and djni.h.
Add a NEWS entry for deferred selection, process-lifetime selection, host attachment and the changed behavior. Qualify the retry wording in ?.jinit for musl, and state in the integration README what discovery and references now guarantee on musl, with the reproduction that led to it.
glibc holds its loader lock only until dl_iterate_phdr() returns, and dlclose() unmaps under that lock, so another thread may unmap any image, Java or not, right after. find_jvms() read the dynamic symbol tables of every listed image after the call returned, through saved addresses and segment ranges, and could fault on a library unmapped meanwhile. Identify JVM images inside the callback and keep only those, with their dynamic section and first segment as addresses that are compared, not read. JNI_GetCreatedJavaVMs is called only while a reference holds the image mapped, except on musl, which never unmaps an image. Attaching now requires the reference before calling into the image. Add a Linux race scenario: a preloaded fixture interposes dl_iterate_phdr() to unmap an unrelated library right after rJava lists the images, and maps and unmaps it on another thread while discovery runs. Compile the fixture with -Werror and show -Wall -Wextra warnings of djni.c.
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.
Hi Simon,
Following up on #334, this builds on your
feature/dynamicwork and extends deferred JVM loading across Windows, macOS and Linux.The goal is the same as in our original discussion: simply loading rJava’s namespace—whether through
library(rJava)or inadvertently through autocomplete—should not choose the JVM. Users could configure Java afterwards, before its first use, without restarting R merely because rJava was loaded. This does not enable switching an already loaded JVM.The implementation was developed with GPT‑6.1 Sol, reviewed over multiple rounds with Claude 5.5, and given a final independent Fable 5.1 review. It has been extensively tested on GitHub-hosted Windows, macOS and Linux runners with multiple JDKs, including targeted checks for Windows dependencies, JavaGD, hosted JVMs and Linux loader/filesystem cases. All current checks pass; the fork’s validation PR contains the tests, run links and documented limitations.
I’m not an expert in JNI or platform loader internals, so I don’t want to overstate what passing tests and AI reviews establish. I would really appreciate your assessment of the implementation and whether you would consider merging it. Hopefully this provides a useful continuation of your work and a smoother experience for rJava and rJavaEnv users.
Thank you!