Run Rscript from Spring MVC with Wildfly 9

2019-08-10 13:53发布

问题:

I am trying to run Rscript from JAVA code. I am able to do so. Now I am trying to run same JAVA code from a Spring MVC project and using Wildfly 9 to run the project. For the first time when I am trying to execute JAVA code (to run Rscript) is working fine and giving correct result, but on running 2nd time it is giving error and Wildfly stops running. Below is the error that I am getting:

A fatal error has been detected by the Java Runtime Environment:
Internal Error (0xc0000029), pid=6768, tid=8456
JRE version: Java(TM) SE Runtime Environment (7.0_75-b13) (build 1.7.0_75-b13)
Java VM: Java HotSpot(TM) Client VM (24.75-b04 mixed mode, sharing windows-x86 )
Problematic frame:
C  [ntdll.dll+0xa096a]
Failed to write core dump. Minidumps are not enabled by default on client versions of Windows

The JAVA code is below:

package com.test.util;

import org.rosuda.JRI.Rengine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RunRScript {
private static final Logger logger = LoggerFactory
        .getLogger(RunRScript.class);

public void runScript() {
    // Create an R vector in the form of a string.
    String javaVector = "c(1,2,3,4,5)";

    // Start Rengine.
    Rengine engine = new Rengine(new String[] { "--no-save" }, false, null);

    // The vector that was created in JAVA context is stored in 'rVector' which is a variable in R context.
    engine.eval("rVector=" + javaVector);

    //Calculate MEAN of vector using R syntax.
    engine.eval("meanVal=mean(rVector)");

    //Retrieve MEAN value
    double mean = engine.eval("meanVal").asDouble();

    //Print output values
    logger.info("Mean of given vector is=" + mean);        
}
}

I am using Windows 8 64-bit and R-2.15.0. Please let me know if my question is not clear or you need any other information. Thanks in advance.

回答1:

You can't call JRI engine with that code. According to the documentation, JRI doesn't allow more that one engine instance per JVM, so you shouldn't create more than one engine.

This line:

// Start Rengine.
Rengine engine = new Rengine(new String[] { "--no-save" }, false, null);

Must be called only once. You have to ensure that only one engine is started in your JVM.

On the other hand, JRI uses by default the same environment to handle all the calls (eval, assign, etc...) so the rest of your code must be synchronized, otherwise you can suffer race conditions every time two different threads are executing eval methods.

If you have trouble getting it working, you can replace JRI by Rserve, that doesn't need JRI library loaded into the JVM and allow concurrency (each thread must use its own RConnection).

But with Rserve you should setup your engine only once, as well as using JRI.

You can use a @PostConstruct method:

/**
 * This method initializes REngine properly and make all the operations needed 
 * to set up the environment.
 * 
 * This RServe implementation must run R in a separate process and check the connection.
 * 
 */
@PostConstruct
public void setUpR() {//NOSONAR

    REngine engine = null;
    try {
        if(LOGGER.isInfoEnabled()) {
            LOGGER.info("Starting RServe process...");
        }
        ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c", String.format("echo 'library(Rserve);Rserve(FALSE,args=\"--no-save --slave --RS-conf %s\")'|%s --no-save --slave", rserveConf, rexe));
        builder.inheritIO();
        Process rProcess = builder.start();

        if(LOGGER.isInfoEnabled()) {
            LOGGER.info("Waiting for Rserve to start...");
        }
        int execCodeResult = rProcess.waitFor();

        if(execCodeResult != SUCCESS_CODE) {
            LOGGER.error(String.format("Unexpected error code starting RServe: %d", execCodeResult));
        } else {
            LOGGER.error("RServe started successfully");
        }

        if(LOGGER.isInfoEnabled()) {
            LOGGER.info("Opening connection to RServe daemon....");
        }
        engine = new RConnection();
        if(LOGGER.isInfoEnabled()) {
            LOGGER.info(String.format("Obtaining R server version: %d", ((RConnection)engine).getServerVersion()));
        }

    } catch(Exception e) {
        LOGGER.error("Unexpected error setting up RServe environment", e);
    } finally {
        closeRServeConnection(engine);
    }
}

You can do the same with JRI:

/**
 * This method initializes REngine properly and make all the operations needed 
 * to set up the environment.
 * 
 * This JRI implementation must load JRI library and starts JRIEngine
 * 
 */
@PostConstruct
public void setUpR() {//NOSONAR

    try {
        //make sure JRI lib can be loaded (it must be present in java.library.path parameter)
        //This line is necessary because Rengine.versionCheck() will execute a System.exit if
        //it can't load JRI library.
        System.loadLibrary("jri");
        // just making sure we have the right version of everything
        if (!Rengine.versionCheck()) {
            LOGGER.error("** Version mismatch - Java files don't match library version.");
            LOGGER.error(String.format("Invalid versions. Rengine must have the same version of native library. Rengine version: %d. RNI library version: %d", Rengine.getVersion(), Rengine.rniGetVersion()));
        }

        // Enables debug traces
        Rengine.DEBUG = 1;

        if(LOGGER.isInfoEnabled()) {
            LOGGER.info("Creating Rengine (with arguments)");
        }
        // 1) we pass the arguments from the command line
        // 2) we won't use the main loop at first, we'll start it later
        // (that's the "false" as second argument)
        // 3) no callback class will be used
        this.engine = REngine.engineForClass("org.rosuda.REngine.JRI.JRIEngine", new String[] { "--no-save" }, new REngineStdOutCallback(LOGGER), false);
        if(LOGGER.isInfoEnabled()) {
            LOGGER.info("Rengine created...");
            LOGGER.info("Loading blockFunction from " + getBlockFunction());
        }

        REXP result = engine.parseAndEval(getBlockFunction());
        if(result == null) {
            LOGGER.error("blockFunction is not loaded!");
        } else if(LOGGER.isInfoEnabled()) {
            LOGGER.info("blockFunction loaded successfully");
        }
    } catch(Exception|UnsatisfiedLinkError e) {
        LOGGER.error("Unexpected error setting up R", e);
    }
}

And then reuse the same Rengine instance in each call (make sure you synchronize the access to this instance).

You have more examples in this repo