Reputation: 6824
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:
Upvotes: 1
Views: 197
Reputation: 311052
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.runner%d
.You don't need multiple Registry ports or even multiple Registries.
Upvotes: 3