blob: bbb9fc1042d3c8afff9f8237151e618f500afd83 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2019 CEA LIST and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* CEA LIST - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.papyrus.ease.lang.python.jupyter.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Platform;
import org.eclipse.ease.AbstractReplScriptEngine;
import org.eclipse.ease.Logger;
import org.eclipse.ease.Script;
import org.eclipse.ease.ScriptEngineException;
import org.eclipse.ease.ScriptExecutionException;
import org.eclipse.ease.debugging.ScriptStackTrace;
import org.eclipse.ease.lang.python.py4j.internal.IInteractiveReturn;
import org.eclipse.ease.lang.python.py4j.internal.IPythonSideEngine;
import org.eclipse.ease.tools.RunnableWithResult;
import org.eclipse.papyrus.ease.lang.python.jupyter.Activator;
import org.eclipse.swt.widgets.Display;
public class JupyterEngine extends AbstractReplScriptEngine {
/**
* The ID of the Engine, to match the declaration in the plugin.xml
*/
public static final String ENGINE_ID = "org.eclipse.ease.lang.python.jupyter.engine";
private static final String NOTE_BOOK_EXTENSION = "ipynb";
private static final long PYTHON_SHUTDOWN_TIMEOUT_SECONDS = 5;
protected IPythonSideEngine fPythonSideEngine;
private Process fPythonProcess;
private Thread fInputGobbler, fErrorGobbler;
private JupyterProxy jupyterProxy;
/**
* Standard StreamGobbler.
*/
private static class StreamGobbler implements Runnable {
private final InputStream fReader;
private final OutputStream fWriter;
private final String fStreamName;
public StreamGobbler(final InputStream stream, final OutputStream output, final String streamName) {
fReader = stream;
fWriter = output;
fStreamName = streamName;
}
@Override
public void run() {
try {
final byte[] bytes = new byte[512];
int readCount;
while ((readCount = fReader.read(bytes)) >= 0) {
try {
fWriter.write(bytes, 0, readCount);
} catch (final IOException e) {
Logger.error(Activator.PLUGIN_ID, "Failed to write data read from Python's " + fStreamName + " stream.", e);
}
}
} catch (final IOException e) {
Logger.error(Activator.PLUGIN_ID, "Failed to read data from Python's " + fStreamName + " stream.", e);
}
}
}
public JupyterEngine() {
super("Jupyter (CEA)");
}
@Override
protected void setupEngine() throws ScriptEngineException {
if( fPythonSideEngine != null )
return;
if (jupyterProxy == null) {
initializeJupyterProxy();
}
try {
setTerminateOnIdle(false);
fPythonProcess = jupyterProxy.getJupyterProcess( );
Activator.getDefault().registeProxy(jupyterProxy);
fInputGobbler = new Thread(new StreamGobbler(fPythonProcess.getInputStream(), getOutputStream(), "stdout"),
"EASE py4j engine output stream gobbler");
fInputGobbler.start();
fErrorGobbler = new Thread(new StreamGobbler(fPythonProcess.getErrorStream(), getErrorStream(), "stderr"),
"EASE py4j engine error stream gobbler");
fErrorGobbler.start();
jupyterProxy.waitForKernelStartup();
fPythonSideEngine = jupyterProxy.getPythonSideEngine();
} catch (final Exception e) {
teardownEngine();
throw new ScriptEngineException("Failed to start Python process. Please check the setting for the Python interpreter"
+ " in Preferences -> Scripting -> Python Scripting:\n" + e.getMessage(), e);
}
}
protected Script notebookScript;
private void initializeJupyterProxy() {
jupyterProxy = new JupyterProxy();
List<Script> scriptToRemove = new ArrayList<>();
for (Script script : getScheduledScripts()) {
if (script.getCommand() instanceof IFile ) {
IFile scriptFile = (IFile) script.getCommand();
if (NOTE_BOOK_EXTENSION.equals(scriptFile.getFileExtension())){
jupyterProxy.setNotebook(scriptFile);
scriptToRemove.add(script);
notebookScript = script;
}
}
}
getScheduledScripts().removeAll(scriptToRemove);
List<Integer> cellsToRun = new ArrayList<>();
Object argObj = getVariable("argv");
if( argObj instanceof String[]) {
for (String arg : (String[])argObj) {
try {
Integer cellNumber = Integer.parseInt(arg);
cellsToRun.add(cellNumber);
}catch (NumberFormatException e) {
continue;
}
}
}
jupyterProxy.addAutoCells(cellsToRun);
}
@Override
protected Object execute(final Script script, final Object reference, final String fileName, final boolean uiThread) throws Throwable {
if (uiThread) {
// run in UI thread
final RunnableWithResult<Object> runnable = new RunnableWithResult<Object>() {
@Override
public Object runWithTry() throws Throwable {
return internalExecute(script, fileName);
}
};
Display.getDefault().syncExec(runnable);
return runnable.getResultOrThrow();
} else {
return internalExecute(script, fileName);
}
}
protected Object internalExecute(final Script script, final String fileName) throws Throwable, Exception {
IInteractiveReturn interactiveReturn;
if (script.isShellMode()) {
interactiveReturn = fPythonSideEngine.executeInteractive(script.getCode());
} else {
final String code = script.getCode();
interactiveReturn = fPythonSideEngine.executeScript(code, fileName);
}
final Object exception = interactiveReturn.getException();
if (exception instanceof Throwable) {
throw (Throwable) exception;
} else if (exception != null) {
throw new ScriptExecutionException(exception.toString(), 0, null, null, new ScriptStackTrace(), null);
} else {
return interactiveReturn.getResult();
}
}
@Override
public void terminateCurrent() {
if (notebookScript != null) {
notebookScript.setResult(0);
}
setTerminateOnIdle(true);
}
@Override
protected void teardownEngine() {
// TODO: this clean shutdown isn't working as intended.
// Sometimes (on Linux) the Python process seems to shutdown
// before fully acknowledging the call to teardownEngine, leaving
// us in a worst state than if we shutdown not-cleanly.
// When/if this is resurrected, the fPythonProcess.destroy();
// below should be removed.
// if (fPythonSideEngine != null) {
// // try a clean shutdown
// fPythonSideEngine.teardownEngine();
// }
if (jupyterProxy != null) {
jupyterProxy.shutDown();
}
Activator.getDefault().unRegisterProxy(jupyterProxy);
try {
// Wait until the gobblers have shovelled all their
// inputs before allowing the engine to considered terminated
if (fInputGobbler != null) {
fInputGobbler.join();
}
if (fErrorGobbler != null) {
fErrorGobbler.join();
}
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void registerJar(final URL url) {
throw new UnsupportedOperationException();
}
@Override
protected Object internalGetVariable(final String name) {
return fPythonSideEngine.internalGetVariable(name);
}
@Override
protected Map<String, Object> internalGetVariables() {
return fPythonSideEngine.internalGetVariables();
}
@Override
protected boolean internalHasVariable(final String name) {
return fPythonSideEngine.internalHasVariable(name);
}
@Override
protected void internalSetVariable(final String name, final Object content) {
fPythonSideEngine.internalSetVariable(name, content);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(final Class<T> adapter) {
System.out.print("Adapted to: " + adapter);
if (adapter.isInstance(fPythonProcess)) {
System.out.println(" SUCCESS");
return (T) fPythonProcess;
}
System.out.println(" Failed");
return super.getAdapter(adapter);
}
@Override
public String toString(Object object) {
if (object == null)
return "None";
return super.toString(object);
}
public Process getPythonProcess() {
return fPythonProcess;
}
public void setJupyterProxy(JupyterProxy jupyterProxy) {
this.jupyterProxy= jupyterProxy;
}
}