Skip to content

Commit a63e7d7

Browse files
committed
add .jvmState(), closes #259
1 parent 7ff70e5 commit a63e7d7

7 files changed

Lines changed: 85 additions & 2 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
o Work around a crash in RStudio on UCRT Windows. (#296)
2121

22+
o add .jvmState() reporting the state of the JVM. (#259)
23+
2224

2325
1.0-6 2021-12-10
2426
o remove obsolete autoconf macros

R/jfirst.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"RgetShortArrayCont", "RgetStringArrayCont", "RidenticalRef", "RgetSimpleClassNames",
2323
"RisAssignableFrom", "RpollException", "RsetField", "RthrowException",
2424
"javaObjectCache", "initRJavaTools", "newRJavaLookupTable", "useDynamicSymbols",
25+
"RgetJVMstate",
2526
# .External
2627
"RcreateObject", "RgetStringValue", "RinitJVM", "RtoString", "RcallMethod",
2728
# .C

R/jinit.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,5 @@
230230
} # if #rcp>0
231231
invisible(.jcall("java/lang/System","S","getProperty","java.class.path"))
232232
}
233+
234+
.jvmState <- function() .Call(RgetJVMstate)

man/jinit.Rd

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
\name{jinit}
22
\alias{.jinit}
3+
\alias{.jvmState}
34
\title{
45
Initialize Java VM
56
}
67
\description{
78
\code{.jinit} initializes the Java Virtual Machine (JVM). This
89
function must be called before any rJava functions can be used.
10+
11+
\code{.jvmState() returns the state of the current JVM.}
912
}
1013
\usage{
1114
.jinit(classpath = NULL, parameters = getOption("java.parameters"), ...,
1215
silent = FALSE, force.init = FALSE)
16+
.jvmState()
1317
}
1418
\arguments{
1519
\item{classpath}{Any additional classes to include in the Java class
@@ -33,6 +37,20 @@ silent = FALSE, force.init = FALSE)
3337
initialization and positive values signify partially successful
3438
initilization (i.e. the VM is up, but parameters or class path could
3539
not be set due to an existing or incompatible VM).
40+
41+
\code{.jvmState} returns a named list with at least the following
42+
elements:
43+
\item{initialized}{\code{TRUE} if rJava is initialized and has a
44+
runing JVM, \code{FALSE} otherwise.}
45+
\item{state}{string representing the current state of the JVM. One of
46+
the following values:
47+
\code{"none"} if there is no JVM, \code{"created"} if the current
48+
JVM has been created by rJava, \code{"attached"} if rJava attached
49+
into an existing JVM (typically when R is embedded into a running
50+
JVM via JRI), \code{"detached"} if there is a JVM (such as embedded
51+
R), but rJava has not been initialized to use it, \code{"dead"} if
52+
the process is about to die due to the JVM forcing en exit or
53+
\code{"destroyed"} if a JVM existed before, but was destroyed.}
3654
}
3755
\details{
3856
Starting with version 0.5 rJava provides a custom class loader that can
@@ -59,7 +77,18 @@ silent = FALSE, force.init = FALSE)
5977
At any rate, it is impossible to change any other VM parameters of a
6078
running VM, so when using \code{.jinit} in a package, be generous with
6179
limits and don't use VM parameters to unnecessarily restrict
62-
resources (or preferably use \code{\link{.jpackage}} instead).
80+
resources (or preferably use \code{\link{.jpackage}} instead). JVM
81+
parameters can only be set if the initial state of the JVM is
82+
\code{"none"}.
83+
84+
There is a subtle difference between \code{"initialized"} and the JVM
85+
state. It is in theory possible for \code{"initialized"} to be
86+
\code{FALSE} and still \code{"state"} to be \code{"created"} or
87+
\code{"attached"} in case where JVM was created but rJava has not been
88+
able to initialize for other reasons, although such state should be
89+
rare and problematic in either case. Behavior of rJava functions other
90+
than \code{.jinit} and \code{.jvmState} is undefined unless
91+
\code{.jvmState()$initialized} is \code{TRUE}.
6392
}
6493
\seealso{
6594
\code{\link{.jpackage}}
@@ -69,6 +98,7 @@ silent = FALSE, force.init = FALSE)
6998
## set heap size limit to 512MB (see java -X) and
7099
## use "myClasses.jar" as the class path
71100
.jinit(classpath="myClasses.jar", parameters="-Xmx512m")
101+
.jvmState()
72102
}
73103
}
74104
\keyword{interface}

src/Rglue.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,3 +1124,24 @@ END_RJAVA_CALL
11241124
INTEGER(res)[0]=tr;
11251125
return res;
11261126
}
1127+
1128+
extern int existingJVMs(); /* init.c */
1129+
1130+
REPC SEXP RgetJVMstate() {
1131+
const char *names[] = { "initialized", "state", "" };
1132+
SEXP res = PROTECT(Rf_mkNamed(VECSXP, names));
1133+
const char *st = "unknown";
1134+
switch (rJava_JVM_state) {
1135+
case JVM_STATE_NONE: /* could be detached */
1136+
st = (existingJVMs() > 0) ? "detached" : "none";
1137+
break;
1138+
case JVM_STATE_CREATED: st = "created"; break;
1139+
case JVM_STATE_ATTACHED: st = "attached"; break;
1140+
case JVM_STATE_DEAD: st = "dead"; break;
1141+
case JVM_STATE_DESTROYED: st = "destroyed"; break;
1142+
}
1143+
SET_VECTOR_ELT(res, 0, Rf_ScalarLogical(rJava_initialized));
1144+
SET_VECTOR_ELT(res, 1, Rf_mkString(st));
1145+
UNPROTECT(1);
1146+
return res;
1147+
}

src/init.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ JavaVM *jvm;
99
/* this will be set when Java tries to exit() but we carry on */
1010
int java_is_dead = 0;
1111

12+
/* current JVM state */
13+
int rJava_JVM_state = JVM_STATE_NONE;
14+
1215
/* cached, global objects */
1316

1417
jclass javaStringClass;
@@ -78,13 +81,20 @@ static int JNICALL vfprintf_hook(FILE *f, const char *fmt, va_list ap) {
7881
static void JNICALL exit_hook(int status) {
7982
/* REprintf("\nJava requested System.exit(%d), trying to raise R error - this may crash if Java is in a bad state.\n", status); */
8083
java_is_dead = 1;
84+
rJava_JVM_state = JVM_STATE_DEAD;
8185
Rf_error("Java called System.exit(%d) requesting R to quit - trying to recover", status);
8286
/* FIXME: we could do something smart here such as running a call-back
8387
into R ... jump into R event loop ... at any rate we cannot return,
8488
but we don't want to kill R ... */
8589
exit(status);
8690
}
8791

92+
int existingJVMs() {
93+
jsize vms = 0;
94+
JavaVM *jvms[32];
95+
return (JNI_GetCreatedJavaVMs(jvms, 32, &vms) >= 0) ? vms : 0;
96+
}
97+
8898
/* in reality WIN64 implies WIN32 but to make sure ... */
8999
#if defined(_WIN64) || defined(_WIN32)
90100
#include <io.h>
@@ -186,6 +196,7 @@ static int initJVM(const char *user_classpath, int opts, char **optv, int hooks,
186196
if (!eenv)
187197
error("Cannot obtain JVM environment");
188198

199+
rJava_JVM_state = JVM_STATE_CREATED;
189200
#if defined(_WIN64) || defined(_WIN32)
190201
_setmode(0, _O_TEXT);
191202
#endif
@@ -217,6 +228,8 @@ static void *initJVMthread(void *classpath)
217228
thInitResult=initJVM((char*)classpath, jvm_opts, jvm_optv, default_hooks, 0);
218229
if (thInitResult) return 0;
219230

231+
rJava_JVM_state = JVM_STATE_CREATED;
232+
220233
init_rJava();
221234

222235
lenv = eenv; /* we make a local copy before unlocking just in case
@@ -357,7 +370,10 @@ static SEXP RinitJVM_real(SEXP par, int disableGuardPages)
357370
while (i<vms) {
358371
if (jvms[i]) {
359372
if (!(*jvms[i])->AttachCurrentThread(jvms[i], (void**)&eenv, NULL)) {
360-
_dbg(rjprintf("RinitJVM: Attached to existing JVM #%d.\n", i+1));
373+
/* attaching our own created JVM doesn't change it to attached */
374+
if (rJava_JVM_state != JVM_STATE_CREATED)
375+
rJava_JVM_state = JVM_STATE_ATTACHED;
376+
_dbg(rjprintf("RinitJVM: Attached to existing JVM #%d.\n", i+1));
361377
break;
362378
}
363379
}
@@ -389,6 +405,8 @@ static SEXP RinitJVM_real(SEXP par, int disableGuardPages)
389405
_dbg(rjprintf("RinitJVM(threads): attach\n"));
390406
/* since JVM was initialized by another thread, we need to attach ourselves */
391407
(*jvm)->AttachCurrentThread(jvm, (void**)&eenv, NULL);
408+
if (rJava_JVM_state != JVM_STATE_CREATED)
409+
rJava_JVM_state = JVM_STATE_ATTACHED;
392410
_dbg(rjprintf("RinitJVM(threads): done.\n"));
393411
r = thInitResult;
394412
#else
@@ -752,6 +770,7 @@ REP void doneJVM() {
752770
(*jvm)->DestroyJavaVM(jvm);
753771
jvm = 0;
754772
eenv = 0;
773+
rJava_JVM_state = JVM_STATE_DESTROYED;
755774
}
756775

757776
/**

src/rJava.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ REPC SEXP RgetSimpleClassNames( SEXP, SEXP );
136136
extern JavaVM *jvm;
137137
extern int rJava_initialized;
138138

139+
#define JVM_STATE_NONE 0 /* no JVM */
140+
#define JVM_STATE_CREATED 1 /* JVM was created by us */
141+
#define JVM_STATE_ATTACHED 2 /* we attached to another JVM */
142+
#define JVM_STATE_DEAD 4 /* set when Java exit handler was called */
143+
#define JVM_STATE_DESTROYED 8 /* JVM was destroyed */
144+
145+
extern int rJava_JVM_state;
146+
139147
extern int java_is_dead;
140148

141149
extern jclass javaStringClass;

0 commit comments

Comments
 (0)