/*- * Copyright 2015 Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package uk.ac.diamond.scisoft.python; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** * This creates and manages input and output to a Python subprocess */ public class PythonSubProcess { // arguments to create a python server which reads a line from standard input and exec() that line private static final String[] args = { "-c", "import sys\n" + "while True:\n" + " print 'READY'\n" + " sys.stdout.flush()\n" + " l = sys.stdin.readline()\n" + " if not l:\n" + " break\n" + " exec l\n", }; private Process p; private StreamHandler obr; private StreamHandler ebr; private OutputStreamWriter stdin; private static final String READY = "READY"; private static final int TIMEOUT = 5; /** */ public PythonSubProcess() { this(null, null); } /** * @param exec path to Python executable * @param env */ public PythonSubProcess(String exec, Map<String, String> env) { if (exec == null) { exec = "python"; } List<String> cmds = new ArrayList<String>(); cmds.add(exec); for (String a : args) { cmds.add(a); } ProcessBuilder pb = new ProcessBuilder(cmds); if (env != null && env.size() > 0) { pb.environment().putAll(env); } try { p = pb.start(); stdin = new OutputStreamWriter(p.getOutputStream()); // wrap output streams to ensure process does not deadlock obr = new StreamHandler(p.getInputStream()); ebr = new StreamHandler(p.getErrorStream()); obr.start(); ebr.start(); // make sure it's ready String l = obr.readLine(); if (!READY.equals(l)) { throw new IllegalStateException("Problem with python subprocess not being ready: " + l + "; " + ebr.readLine(TIMEOUT)); } } catch (Throwable t) { throw new IllegalStateException(t); } } /** * Wrapper thread to asynchronously get lines from given stream */ class StreamHandler extends Thread { private BufferedReader stream; private boolean alive; BlockingQueue<String> out; public StreamHandler(InputStream in) { stream = new BufferedReader(new InputStreamReader(in)); alive = true; out = new LinkedBlockingQueue<String>(); } @Override public void run() { while (alive) { try { String l = stream.readLine(); if (l == null) { // die if stream has ended alive = false; } else { out.add(l); } } catch (IOException e) { } } } @Override public void interrupt() { alive = false; super.interrupt(); } /** * Clear internally stored content */ public void clear() { out.clear(); } /** * Read line * @param milliSeconds * @return line */ public String readLine(int milliSeconds) { if (alive) { try { return out.poll(milliSeconds, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { } } return null; } /** * Blocking read line * @return line */ public String readLine() { if (alive) { try { return out.take(); } catch (InterruptedException e) { } } return null; } } /** * Send command and retrieve results * @param text * @return output lines and error - each string is empty or concatenated lines or null when there is no error */ public String[] communicate(String text) { send(text); List<String> results = new ArrayList<String>(); String l; while (true) { l = obr.readLine(); if (READY.equals(l)) { break; } results.add(l); } StringBuilder out = new StringBuilder(); for (String r : results) { out.append(r); out.append('\n'); } String[] lines = new String[] {out.toString(), null}; results.clear(); while (true) { l = ebr.readLine(TIMEOUT); if (l == null) break; results.add(l); } if (results.size() > 0) { out.setLength(0); for (String r : results) { out.append(r); out.append('\n'); } lines[1] = out.toString(); } return lines; } private void send(String text) { try { obr.clear(); ebr.clear(); stdin.write(text); stdin.flush(); } catch (IOException e) { } } /** * Stop subprocess */ public void stop() { try { stdin.close(); } catch (IOException e) { } } }