Search code examples
javamatlabservletsmatlab-compilermatlab-java

Why can't I execute different Matlab functions from Java concurrently?


I have two Java Servlets: DataFetcherServlet and UploaderServlet. Both servlets call 2 different Java methods which in turn call their corresponding Matlab functions through JNI, and each of which was compiled into a separate Java jar file to use as a library. The application is powered by AJAX to create a Desktop like feel. For the UploaderServlet, the users can upload an excel file to this servlet, the parsed data then get passed to a Java method which then calls the compiled Matlab function to generate and save alot of images (currently over 5000 images), because this will take alot of time, I use an ExecutorService to execute it in the background. But new requests sent the DataFetcherServlet which will also call another compiled Matlab function is blocked until the image generation part is completed. I don't know why it is blocking new requests even though the requests are sent to a different servlet.

DataFetcherServlet.java

public class DataFetcherServlet extends HttpServlet {

    @Inject
    private CdfReader reader; // An EJB to get a data array from Matlab

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        try {
            String filePath = "path/to/file";
            Object[] result = reader.read(filePath); // reader.read() is just a wrapper around the method in the jar file mentioned above that actually calls the matlab function to return an array of number
            MWNumericArray array = (MWNumericArray)result[0] // This will block while the other Matlab function is generating the images.
            .
            .
            .
        } catch (MWException ex) {
            Logger.getLogger(DataFetcherServlet.class.getName()).log(Level.SEVERE, null, ex);
    }
}

UploaderServlet.java

public class UploaderServlet extends HttpServlet {
    @Inject
    private ExcelIonImageGenerator generator; // An EJB to call Matlab to generate the images

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        try {
            String dir = "path/to/parent/directory";
            Path excel = Paths.get(dir+ "excel", part.getSubmittedFileName()); // Path to where the uploaded excel file is stored
            if (!Files.exists(excel))
                Files.copy(part.getInputStream(), excel);
            // ExcelExtractor is a helper class to parse the excel file.
            Double[][] ranges = ExcelExtractor.extractSheet(WorkbookFactory.create(excel.toFile()));
            // This will call a Java library method which in turns call the Matlab function
            // to generate the images (over 5000 in this case)
            // See the code for this method below.
            generator.generate(dir+ "images" + File.separator, ranges);
        } catch (MWException | InvalidFormatException ex) {
            Logger.getLogger(UploaderServlet.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

ExcelIonImageGenerator.java

import com.mathworks.toolbox.javabuilder.*; // Matlab SDK needed to integrate with Java
import java.util.concurrent.*;
import java.util.logging.*;
import javax.annotation.PreDestroy;
import javax.ejb.Stateless;
import save_ion_image_for_all_ranges_in_spreadsheet.Class1; // The jar file which contains code to call Matlab code through JNI

@Stateless
public class ExcelIonImageGenerator {
    private final Class1 clazz1;
    private ExecutorService pool;

    public ExcelIonImageGenerator() throws MWException {
        clazz1 = new Class1();
        pool = Executors.newFixedThreadPool(1);
    }

    public void generate(String path, Double[][] ranges) throws MWException {
        // Submit this task to the ExecutorService so it can be processed
        // in a different thread than the caller thread
        pool.submit(() -> generateHelper(path, ranges, clazz1), 1);
    }

    private void generateHelper(String path, Double[][] ranges, Class1 clazz) {
        try {
            // This method was generated by Matlab tool, it calls the native
            // Matlab code through JNI, and it will block any request that will call
            // other Matlab functions until it finishes.
            clazz.save_ion_image_for_all_ranges_in_spreadsheet(path, ranges);
        } catch (MWException ex) {
            Logger.getLogger(ExcelIonImageGenerator.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Solution

  • You have three options:

    1. Launch multiple processes of your Java application that makes calls to Matlab. Calls from a single process use the same MCR which has a process-wide lock, however calls from different processes would run on separate MCR computational engines.
    2. Use Matlab Production Server, which basically facilitates the use of multiple MCRs. This is a toolkit that requires a separate license and installation.
    3. You don't necessarily have to limit yourself to running MCR / compiled code unless you have very specific performance concerns. You can actually install Matlab itself on the server, launch multiple instances (headless, etc) from the same Java process, and communicate with them e.g. through MatlabControl or the new official MATLAB Engine API for Java.

    There's a very good answer from MathWorks Support Team on MatlabCentral, explaining these limitations of MCR in detail.