package geniusweb.javavenv; import java.io.IOException; import java.io.InputStream; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; /** * A straight python call. Handles all the connection stuff: connecting to the * input and output streams so that we can try to follow what python is doing. * NOTE generally this is for internal use, instead use {@link PythonVenv} */ public class Call { private final List allargs = new LinkedList<>(); // size 2, we expect both stdin and stderr to always post something here. private final BlockingQueue result = new ArrayBlockingQueue( 2); private final int deadlinems; /** * Call python with given arguments. call {@link #run()} to run. We * recommend thatn you use {@link PythonVenv} to run commands instead of * this direct call. * * @param pythonexe a working python3 exe, typically * "/usr/local/bin/python3" or something in a venv on temp * disk. * @param args the other args for the call * @param deadlinems the deadline (ms) after which the call should be * cancelled. */ public Call(String pythonexe, List args, int deadlinems) { if (pythonexe == null || args == null) throw new IllegalArgumentException("args must be not null"); allargs.add(pythonexe); allargs.addAll(args); this.deadlinems = deadlinems; } /** * * @return the result of the python call (that is, what the program printed * out) * @throws InterruptedException if the call does not terminate in time. * @throws IOException if we can not connect properly to the python * process * @throws PythonError if the python program printed to stderr, eg * when an error occured. We try to capture all * the error lines as text in the pythonerror * object. */ public String run() throws InterruptedException, IOException, PythonError { ProcessBuilder processBuilder = new ProcessBuilder(allargs); // System.out.println("all args " + allargs); Process p; p = processBuilder.start(); Runnable stdinreader = new Runnable() { @Override public void run() { String totalreply = ""; byte[] cbuf = new byte[500]; InputStream is = p.getInputStream(); try { while (true) { int n = is.read(cbuf); if (n == -1) break; totalreply += new String(cbuf, 0, n, "UTF8"); } result.add(totalreply); } catch (IOException e) { result.add(e); } } }; Runnable errorlogger = new Runnable() { @Override public void run() { byte[] cbuf = new byte[500]; InputStream is = p.getErrorStream(); String totalerror = ""; try { while (true) { int n = is.read(cbuf); if (n == -1) break; totalerror += new String(cbuf, 0, n, "UTF8"); } if (!totalerror.isEmpty()) result.add(new PythonError(totalerror)); else result.add(""); } catch (IOException e) { result.add(e); } } }; new Thread(stdinreader).start(); new Thread(errorlogger).start(); // now we wait till both are finished. // wait till both are finished. Order is irrelevant. Object res1 = result.poll(deadlinems, TimeUnit.MILLISECONDS); if (res1 == null) throw new InterruptedException("Python code failed to return"); Object res2 = result.poll(100, TimeUnit.MILLISECONDS); if (res2 == null) throw new InterruptedException( "Second python channel failed to close"); // If there is a non-empty error, return it, otherwise return res rethrowIfException(res1); rethrowIfException(res2); if (!((String) res1).isEmpty()) return (String) res1; return (String) res2; } @Override public String toString() { return "python " + allargs; } private void rethrowIfException(Object obj) throws IOException, PythonError { if (obj instanceof Exception) { throw new PythonError("Failed to execute " + this, (Exception) obj); } } }