Skip to content

Commit

Permalink
add dynamic JNI load/unload support
Browse files Browse the repository at this point in the history
  • Loading branch information
s-u committed Sep 18, 2024
1 parent 958a759 commit e127640
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 18 deletions.
2 changes: 1 addition & 1 deletion R/jfirst.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"RgetShortArrayCont", "RgetStringArrayCont", "RidenticalRef", "RgetSimpleClassNames",
"RisAssignableFrom", "RpollException", "RsetField", "RthrowException",
"javaObjectCache", "initRJavaTools", "newRJavaLookupTable", "useDynamicSymbols",
"RgetJVMstate",
"RgetJVMstate", "RloadJVM", "RunloadJVM",
# .External
"RcreateObject", "RgetStringValue", "RinitJVM", "RtoString", "RcallMethod",
# .C
Expand Down
33 changes: 33 additions & 0 deletions R/jinit.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,41 @@
.need.init <- function()
.Call(RJava_needs_init)

.jloadJVM <- function(path, silent=FALSE)
.Call(RloadJVM, path.expand(path), silent)

.junloadJVM <- function()
.Call(RunloadJVM)

.jfindAndLoadJVM <- function() {
home <- Sys.getenv("JAVA_HOME")
if (!nzchar(home) || !isTRUE(dir.exists(home))) {
## try java_home first if present
if (file.exists("/usr/libexec/java_home")) {
home <- tryCatch(system("/usr/libexec/java_home", intern=TRUE, ignore.stderr=TRUE),
error=function(...) "")
}
## otherwise rely on java in PATH
if (!nzchar(home) || !isTRUE(dir.exists(home))) {
home <- tryCatch(system(paste("java", "-cp",
shQuote(system.file("java", package="rJava")), "getsp", "java.home"),
intern=TRUE, ignore.stderr=TRUE), error=function(...) "")
}
}
if (!nzchar(home) || !isTRUE(dir.exists(home)))
stop("Cannot find Java. Either make sure java executable is on the PATH or set the JAVA_HOME environment variable.")
libjvm <- list.files(home, "^libjvm[.]", recursive=TRUE, full.names=TRUE)
if (!length(libjvm))
stop("Cannot find libjvm in ", home)
message("Loading JVM from ", home)
.jloadJVM(libjvm)
}

## initialization
.jinit <- function(classpath=NULL, parameters=getOption("java.parameters"), ..., silent=FALSE, force.init=FALSE) {
st <- .jvmState()
if (st$state == "not-loaded")
.jfindAndLoadJVM()
running.classpath <- character()
if (!.need.init()) {
running.classpath <- .jclassPath()
Expand Down
1 change: 1 addition & 0 deletions mkdist
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ echo "Copy compiled Java classes ..."
# copy all complied Java classes and sources
cp src/java/*.class inst/java
cp src/java/*.java inst/java
cp tools/getsp* inst/java
# move RJavaClassLoader into boot area since it will be loaded by the system class loader
mv inst/java/RJavaClassLoader* inst/java/boot
# move javadoc directory
Expand Down
35 changes: 33 additions & 2 deletions src/Rglue.c
Original file line number Diff line number Diff line change
Expand Up @@ -1135,8 +1135,11 @@ REPC SEXP RgetJVMstate(void) {
const char *st = "unknown";
switch (rJava_JVM_state) {
case JVM_STATE_NONE: /* could be detached */
st = (existingJVMs() > 0) ? "detached" : "none";
break;
{
int evm = existingJVMs();
st = (evm == -1) ? "not-loaded" : ((evm > 0) ? "detached" : "none");
break;
}
case JVM_STATE_CREATED: st = "created"; break;
case JVM_STATE_ATTACHED: st = "attached"; break;
case JVM_STATE_DEAD: st = "dead"; break;
Expand All @@ -1147,3 +1150,31 @@ REPC SEXP RgetJVMstate(void) {
UNPROTECT(1);
return res;
}

REPC SEXP RloadJVM(SEXP sPath, SEXP sSilent) {
const char *path;
int res, silent = Rf_asInteger(sSilent);
if (TYPEOF(sPath) != STRSXP || LENGTH(sPath) != 1)
Rf_error("Invalid path to JVM library");
/* FIXME: worry about encoding? */
path = CHAR(STRING_ELT(sPath, 0));
res = djni_load(path);
if (silent)
return Rf_ScalarInteger(res);
switch(res) {
case -1: Rf_error("JVM run-time is already loaded");
case -2: Rf_error("Cannot load JVM run-time (%s)", djni_last_error() ? djni_last_error() : "unsuccessful");
case -3: Rf_error("JVM run-time does not have required entry points");
}
return Rf_ScalarLogical(1);
}

REPC SEXP RunloadJVM(void) {
int res;
if (rJava_JVM_state == JVM_STATE_CREATED ||
rJava_JVM_state == JVM_STATE_ATTACHED) {
destroyJVM();
}
res = djni_unload();
return Rf_ScalarLogical((res < -1) ? 0 : 1);
}
10 changes: 10 additions & 0 deletions src/djni.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ int djni_load(const char *path) {
JNI_GetDefaultJavaVMInitArgs_fn = 0;
JNI_CreateJavaVM_fn = 0;
JNI_GetCreatedJavaVMs_fn = 0;
dlclose(jni_dl);
jni_dl = 0;
return -3;
}
return 0;
Expand All @@ -54,6 +56,14 @@ int djni_unload(void) {
return -1;
}

int djni_loaded(void) {
return jni_dl ? 1 : 0;
}

const char* djni_last_error(void) {
return last_error;
}

/* The following are the "normal" JNI API calls which are routed to libjvm JNI API.
If no JNI was loaded, they return -99 to distinguish it from JNI error codes. */

Expand Down
5 changes: 5 additions & 0 deletions src/djni.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@

#include "jni.h"

/* returned if no run-time is loaded */
#define JNI_NO_DJNI -99

int djni_load(const char *path);
int djni_unload(void);
const char* djni_last_error(void);
int djni_loaded(void);

jint JNI_GetDefaultJavaVMInitArgs(void *args);
jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
Expand Down
41 changes: 29 additions & 12 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ static void JNICALL exit_hook(int status) {
exit(status);
}

/* -1 if no run-time is loaded, otherwise number of active VMs */
int existingJVMs(void) {
jsize vms = 0;
JavaVM *jvms[32];
return (JNI_GetCreatedJavaVMs(jvms, 32, &vms) >= 0) ? vms : 0;
jint res = JNI_GetCreatedJavaVMs(jvms, 32, &vms);
return (res == JNI_NO_DJNI) ? -1 : ((res >= 0) ? vms : 0);
}

/* in reality WIN64 implies WIN32 but to make sure ... */
Expand All @@ -118,7 +120,9 @@ static int initJVM(const char *user_classpath, int opts, char **optv, int hooks,
if(!user_classpath) user_classpath = "";

vm_args.version = JNI_VERSION_1_2;
if(JNI_GetDefaultJavaVMInitArgs(&vm_args) != JNI_OK) {
if((res = JNI_GetDefaultJavaVMInitArgs(&vm_args)) != JNI_OK) {
if (res == JNI_NO_DJNI) /* DJNI has not loaded a run-time yet */
error("No Java run-time is not loaded.");
error("JNI 1.2 or higher is required");
return -1;
}
Expand Down Expand Up @@ -193,8 +197,11 @@ static int initJVM(const char *user_classpath, int opts, char **optv, int hooks,
if (disableGuardPages && (res != 0 || !eenv))
return -2; /* perhaps this VM does not allow disabling guard pages */

if (res != 0)
error("Cannot create Java virtual machine (JNI_CreateJavaVM returned %ld)", (long int) res);
if (res != 0) {
if (rJava_JVM_state != JVM_STATE_NONE)
Rf_warning("Attempt to re-create a second JVM in a process - most known Java implementations do not support it.");
Rf_error("Cannot create Java virtual machine (JNI_CreateJavaVM returned %d)", (int) res);
}
if (!eenv)
error("Cannot obtain JVM environment");

Expand Down Expand Up @@ -760,21 +767,31 @@ static SEXP RinitJVM_jsw(SEXP par) {
/** RinitJVM(classpath)
initializes JVM with the specified class path */
REP SEXP RinitJVM(SEXP par) {

if (!djni_loaded())
Rf_error("No Java run-time is loaded.");
#ifndef JVM_STACK_WORKAROUND
return RinitJVM_real(par, 0);
return RinitJVM_real(par, 0);
#else
return RinitJVM_jsw(par);
return RinitJVM_jsw(par);
#endif
}

REP void doneJVM(void) {
(*jvm)->DestroyJavaVM(jvm);
jvm = 0;
eenv = 0;
rJava_JVM_state = JVM_STATE_DESTROYED;
/* detaches (if only attached) or destroys (if created) JVM */
HIDE void destroyJVM(void) {
if (!jvm)
return;
if (rJava_JVM_state == JVM_STATE_CREATED ||
rJava_JVM_state == JVM_STATE_ATTACHED) {
(*jvm)->DetachCurrentThread(jvm);
if (rJava_JVM_state == JVM_STATE_CREATED)
(*jvm)->DestroyJavaVM(jvm);
jvm = 0;
eenv = 0;
rJava_JVM_state = (rJava_JVM_state == JVM_STATE_CREATED) ? JVM_STATE_DESTROYED : JVM_STATE_DETACHED;
}
}


/**
* Initializes the cached values of classes and methods used internally
* These classes and methods are the ones that are in rJava (RJavaTools, ...)
Expand Down
8 changes: 5 additions & 3 deletions src/rJava.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#ifndef __RJAVA_H__
#define __RJAVA_H__

#define RJAVA_VER 0x01000b /* rJava v1.0-11 */
#define RJAVA_VER 0x010010 /* rJava v1.1-0 */

/* important changes between versions:
3.0 - adds compiler
Expand All @@ -19,7 +19,7 @@
0.2 - uses S4 classes
0.1 - first public release */

#include <jni.h>
#include "djni.h"
#include <R.h>
#include <Rinternals.h>
#include <Rversion.h>
Expand Down Expand Up @@ -140,7 +140,8 @@ extern int rJava_initialized;
#define JVM_STATE_CREATED 1 /* JVM was created by us */
#define JVM_STATE_ATTACHED 2 /* we attached to another JVM */
#define JVM_STATE_DEAD 4 /* set when Java exit handler was called */
#define JVM_STATE_DESTROYED 8 /* JVM was destroyed */
#define JVM_STATE_DETACHED 8 /* detached from existing JVM */
#define JVM_STATE_DESTROYED 9 /* JVM was destroyed */

extern int rJava_JVM_state;

Expand Down Expand Up @@ -168,6 +169,7 @@ extern jmethodID mid_RJavaImport_lookup ;
extern jmethodID mid_RJavaImport_exists ;

HIDE void init_rJava(void);
HIDE void destroyJVM(void);

/* in otables.c */
// turn this for debugging in otables.c
Expand Down

0 comments on commit e127640

Please sign in to comment.