package de.mpicbg.knime.scripting.matlab.ctrl; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import matlabcontrol.MatlabConnectionException; import matlabcontrol.MatlabInvocationException; import matlabcontrol.MatlabProxy; import matlabcontrol.MatlabProxyFactory; import matlabcontrol.MatlabProxyFactoryOptions; /** * This class can be considered to be a local MATLAB server for MATLAB scripting-integration * plugin for KNIME. It manages a single running MATLAB application that can be controlled * by the KNIME nodes. * * TODO: add connection timeout * TODO: Connection problems are not handled well yet: * if there is not network and MATLAB can't check out a license it hangs and if interrupted, thinks that matlab already runs. * * @author Felix Meyenhofer */ public class MatlabConnector { /** keep one single class instance */ private static MatlabConnector instance; /** Total count of threads connecting to MATLAB */ private static Integer threadCount = 0; /** Factory to control the MATLAB session */ static MatlabProxyFactory proxyFactory; /** MATLAB access queue */ static ArrayBlockingQueue<MatlabProxy> proxyQueue = new ArrayBlockingQueue<MatlabProxy>(10); /** Number of allowed proxies (set during instantiation of the class) */ static int proxyQueueSize = 1; /** Difference of the currently available MATLAB sessions and the set quota */ private static int proxyQueueDifference = 0; /** * Constructor */ private MatlabConnector() { // Prevent multiple instantiation } /** * Get the instance of the MatlabConnector * * @param numberOfMatlabSessions allowed * @return the Matlab connector singleton * @throws MatlabConnectionException * @throws MatlabInvocationException * @throws InterruptedException */ public static synchronized MatlabConnector getInstance(int numberOfMatlabSessions) throws MatlabConnectionException, MatlabInvocationException, InterruptedException { // Initialize ONCE if necessary and set the queue size and difference. if (MatlabConnector.instance == null) { MatlabConnector.instance = new MatlabConnector(); proxyQueueSize = numberOfMatlabSessions; proxyQueueDifference = proxyQueue.size() - proxyQueueSize; } // Determine the total number of threads and the number of this thread threadCount++; // Create the proxy factory (exactly once). if (proxyFactory == null) { MatlabProxyFactoryOptions options = new MatlabProxyFactoryOptions.Builder(). setUsePreviouslyControlledSession(true). build(); proxyFactory = new MatlabProxyFactory(options); } return MatlabConnector.instance; } /** * Setter for the number of Matlab instances * @param newSize */ public static synchronized void setProxyQueueSize(int newSize) { if (newSize != proxyQueueSize) { proxyQueueDifference = proxyQueueSize - newSize; proxyQueueSize = newSize; } } /** * Update the Matlab instance queue * * @throws MatlabConnectionException * @throws MatlabInvocationException * @throws InterruptedException */ private static synchronized void updateProxyQueue() throws MatlabConnectionException, MatlabInvocationException, InterruptedException { for (int i = 0; i < Math.abs(proxyQueueDifference); i++) { if (proxyQueueDifference < 0) { requestMatlabProxy(); } else { MatlabProxy proxy = proxyQueue.take(); proxy.disconnect(); proxyQueue.remove(proxy); } } proxyQueueDifference = 0; } /** * Try to get a proxy for a Matlab instance * @throws MatlabConnectionException * @throws MatlabInvocationException * @throws InterruptedException */ private static synchronized void requestMatlabProxy() throws MatlabConnectionException, MatlabInvocationException, InterruptedException { System.out.println("MATLAB: starting new session..."); proxyFactory.requestProxy(new MatlabProxyFactory.RequestCallback() { @Override public void proxyCreated(MatlabProxy proxy) { // Once the session is running, add the proxy object to the proxy queue proxyQueue.add(proxy); // Add a disconnection listener, so this controller can react if the MATLAB // application is closed (for instance by the user) proxy.addDisconnectionListener(new MatlabProxy.DisconnectionListener() { @Override public void proxyDisconnected(MatlabProxy proxy) { proxyQueue.remove(proxy); proxyQueueDifference--; System.out.println("MATLAB application disconnected. Remaining running sessions: " + proxyQueue.size() + ", session difference: " + proxyQueueDifference); } }); } }); cleanupMatlabConsole(); } /** * Utility to clean the output in the Matlab console */ private static synchronized void cleanupMatlabConsole() { // Move the proxies from the blocking queue to a temporary list List<MatlabProxy> tempProxyHolder = new ArrayList<MatlabProxy>(proxyQueueSize); for (int i = 0; i < proxyQueueSize; i++) { try { tempProxyHolder.add(proxyQueue.take()); } catch (InterruptedException e2) { e2.printStackTrace(); } } // Execute the MATLAB commands and move the proxies back to the array blocking queue. for (int i = 0; i < proxyQueueSize; i++) { try { MatlabProxy proxy = tempProxyHolder.remove(0); proxy.eval("clc;"); proxy.eval("disp('Started from KNIME (MATLAB scripting integration)');"); proxyQueue.put(proxy); } catch (MatlabInvocationException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * Get the number of threads * * @return number of threads */ public static synchronized int getReferenceCount() { return threadCount; } /** * Get a proxy from the queue. * This should be succeeded by a call to {@link #returnProxyToQueue} * * @return Proxy object * @throws MatlabConnectionException * @throws InterruptedException * @throws MatlabInvocationException */ public MatlabProxy acquireProxyFromQueue() throws MatlabConnectionException, MatlabInvocationException, InterruptedException { // Update the queue (in case a MATLAB session is closed, we want to restart a new one // if it is requested by the node, and not before!) updateProxyQueue(); // Get access to the session try { return proxyQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException("This is bad. The queue is probably corrupted. You need to close and reopen the Workflow."); } } /** * Put the proxy back into the queue. * This should be preceded by a call to {@link #acquireProxyFromQueue()} * * @param proxy */ public void returnProxyToQueue(MatlabProxy proxy) { try { proxyQueue.put(proxy); } catch (InterruptedException e) { System.err.println("This is bad. The queue is probably corrupted. You need to reopen the Workflow."); e.printStackTrace(); } } }