Matlab Java Interoperability

倖福魔咒の 提交于 2019-12-12 04:23:07

问题


Our web app acts as an integration layer which allows the users to run Matlab code (Matlab is a scientific programming language) which was compiled to Java, packaged up as jar files via browser (selected ones as in above image, except for remote_proxy-1.0.0.jar which is not, it is used for RMI).

The problem is that, Matlab Java runtime, contained inside the javabuilder-1.0.0.jar file, has a process-wide blocking mechanism which means if the first user sends an HTTP request to execute the cdf_read-1.0.0.jar or any Matlab-compiled-to-Java jars at all, then subsequent requests will block until the first one finishes and it will take no less than 5 seconds since JNI is used to invoke the native Matlab code and because the app server just spawns new threads to serve each request, but once again, due to the process-wide locking mechanism of Matlab Java runtime, these newly spawned threads will just block waiting for the first request to be fulfilled, thus our app can technically serve one user at a time.

So to work around this problem, for each such request, we start a new JVM process, send the request to this new process to run the job using RMI, then return the result back to the app server's process, then destroy the spawned process. So we've solved the blocking issue, but this is not very good at all in terms of memory used, this is a niche app, so number of users is in the range of thoudsands. Below is the code used to start a new process to run the BootStrap class which starts a new RMI registry, and binds a remote object to run the job.

package rmi;

import java.io.*;
import java.nio.file.*;
import static java.util.stream.Collectors.joining;
import java.util.stream.Stream;
import javax.enterprise.concurrent.ManagedExecutorService;
import org.slf4j.LoggerFactory;
//TODO: Remove sout
public class ProcessInit {

    public static Process startRMIServer(ManagedExecutorService pool, String WEBINF, int port, String jar) {
        ProcessBuilder pb = new ProcessBuilder();
        Path wd = Paths.get(WEBINF);
        pb.directory(wd.resolve("classes").toFile());
        Path lib = wd.resolve("lib");
        String cp = Stream.of("javabuilder-1.0.0.jar", "remote_proxy-1.0.0.jar", jar)
                .map(e -> lib.resolve(e).toString())
                .collect(joining(File.pathSeparator));
        pb.command("java", "-cp", "." + File.pathSeparator + cp, "rmi.BootStrap", String.valueOf(port));
        while (true) {
            try {
                Process p = pb.start();
                pool.execute(() -> flushIStream(p.getInputStream()));
                pool.execute(() -> flushIStream(p.getErrorStream()));
                return p;
            } catch (Exception ex) {
                ex.printStackTrace();
                System.out.println("Retrying....");
            }
        }
    }

    private static void flushIStream(InputStream is) {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
            br.lines().forEach(System.out::println);
        } catch (IOException ex) {
            LoggerFactory.getLogger(ProcessInit.class.getName()).error(ex.getMessage());
        }
    }
}

This class is used to start a new RMI registry so each HTTP request to execute Matlab code can be run in a separate process, the reason we do this is because each RMI registry is bound to a process, so we need a separate registry for each JVM process.

package rmi;

import java.rmi.RemoteException;
import java.rmi.registry.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.logging.*;
import remote_proxy.*;
//TODO: Remove sout
public class BootStrap {

    public static void main(String[] args) {
        int port = Integer.parseInt(args[0]);
        System.out.println("Instantiating a task runner implementation on port: "  + port );
        try {
            System.setProperty("java.rmi.server.hostname", "localhost");
            TaskRunner runner = new TaskRunnerRemoteObject();
            TaskRunner stub = (TaskRunner)UnicastRemoteObject.exportObject(runner, 0);
            Registry reg = LocateRegistry.createRegistry(port);
            reg.rebind("runner" + port, stub);
        } catch (RemoteException ex) {
            Logger.getLogger(BootStrap.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

This class allows to submit the request to execute the Matlab code, returns the result, and kill the newly spawned process.

package rmi.tasks;

import java.rmi.*;
import java.rmi.registry.*;
import java.util.Random;
import java.util.concurrent.*;
import java.util.logging.*;
import javax.enterprise.concurrent.ManagedExecutorService;
import remote_proxy.*;
import rmi.ProcessInit;

public final class Tasks {

    /**
     * @param pool This instance should be injected using @Resource(name = "java:comp/DefaultManagedExecutorService")
     * @param task This is an implementation of the Task interface, this
     *             implementation should extend from MATLAB class and accept any necessary
     *             arguments, e.g Class1 and it must implement Serializable interface
     * @param WEBINF WEB-INF directory
     * @param jar  Name of the jar that contains this MATLAB function
     * @throws RemoteException
     * @throws NotBoundException
     */
    public static final <T> T runTask(ManagedExecutorService pool, Task<T> task, String WEBINF, String jar) throws RemoteException, NotBoundException {
        int port = new Random().nextInt(1000) + 2000;
        Future<Process> process = pool.submit(() -> ProcessInit.startRMIServer(pool, WEBINF, port, jar));
        Registry reg = LocateRegistry.getRegistry(port);
        TaskRunner generator = (TaskRunner) reg.lookup("runner" + port);
        T result = generator.runTask(task);
        destroyProcess(process);
        return result;
    }

    private static void destroyProcess(Future<Process> process) {
        try {
            System.out.println("KILLING THIS PROCESS");
            process.get().destroy();
            System.out.println("DONE KILLING THIS PROCESS");
        } catch (InterruptedException | ExecutionException ex) {
            Logger.getLogger(Tasks.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("DONE KILLING THIS PROCESS");
        }
    }
}

The questions:

  • Do I have to start a new separate RMI registry and bind a remote to it for each new process?
  • Is there a better way to achieve the same result?

回答1:


  1. You don't want JVM startup time to be part of the perceived transaction time. I would start a large number of RMI JVMs ahead of time, dependending on the expected number of concurrent requests, which could be in the hundreds or even thousands.
  2. You only need one Registry: rmiregistry.exe. Start it on its default port and with an appropriate CLASSPATH so it can find all your stubs and application classes they depend on.
  3. Bind each remote object into that Registry with sequentially-increasing names of the general form runner%d.
  4. Have your RMI client pick a 'runner' at random from the known range 1-N where N is the number of runners. You may need a more sophisticated mechanism than mere randomness to ensure that the runner is free at the time.

You don't need multiple Registry ports or even multiple Registries.



来源:https://stackoverflow.com/questions/42233498/matlab-java-interoperability

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!