Problem
I spent several sleepless nights trying to figure out why
Jython 2.7beta3 suddenly refused to work in Google App Engine while Jython 2.7 beta2 worked nicely.
It crashes while initializing site.py standard package because Google App Engine blocks any attempt to use ProcessBuilder.
com.jythonui.client.service.JythonService.runAction(com.jythonui.shared.RequestContext,com.jythonui.shared.DialogVariables,java.lang.String,java.lang.String)' threw an unexpected exception: Traceback (most recent call last):
File "/base/data/home/apps/s~testjavahotel/5.380117830403911812/WEB-INF/lib/jython-standalone-2.7-b3.jar/Lib/site.py", line 571, in
File "/base/data/home/apps/s~testjavahotel/5.380117830403911812/WEB-INF/lib/jython-standalone-2.7-b3.jar/Lib/site.py", line 552, in main
File "/base/data/home/apps/s~testjavahotel/5.380117830403911812/WEB-INF/lib/jython-standalone-2.7-b3.jar/Lib/site.py", line 231, in check_enableusersite
at jnr.posix.JavaPOSIX.geteuid(JavaPOSIX.java:102)
at jnr.posix.LazyPOSIX.geteuid(LazyPOSIX.java:115)
at org.python.modules.posix.PosixModule.geteuid(PosixModule.java:343)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:45)
java.lang.NoClassDefFoundError: java.lang.NoClassDefFoundError: Could not initialize class jnr.posix.JavaPOSIX$LoginInfo
at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:389)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:579)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:265)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:305)
at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
This exception (raised in Development and Production mode) can be resolved easy by setting
Options.no_user_site = true; before starting the Jython interpreter.
But then came up the second one which unveils only in Production mode (works in Development mode).
javax.servlet.ServletContext log: Exception while dispatching incoming RPC call
com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract com.jythonui.shared.DialogVariables com.jythonui.client.service.JythonService.runAction(com.jythonui.shared.RequestContext,com.jythonui.shared.DialogVariables,java.lang.String,java.lang.String)' threw an unexpected exception: Traceback (most recent call last):
File "__pyclasspath__/site$py.class", line 571, in
File "__pyclasspath__/site$py.class", line 553, in main
File "__pyclasspath__/site$py.class", line 286, in addusersitepackages
File "__pyclasspath__/site$py.class", line 261, in getusersitepackages
File "__pyclasspath__/site$py.class", line 250, in getuserbase
File "__pyclasspath__/sysconfig$py.class", line 112, in
File "__pyclasspath__/posixpath$py.class", line 391, in normpath
AttributeError: 'NoneType' object has no attribute 'startswith'
at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:389)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:579)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteService
Attribute sys.prefix is None for some mysterious reason. Because debugging in Production mode is impossible the only way was to compile Jython from sources (which is not trivial for the first time), add logging messages and trying to encircle the bug. Finally I realized that in Google App Engine Production mode system property "java.class.path" is null. So the code
if (root == null) {
String classpath = preProperties.getProperty("java.class.path");
ll.warning("classpath=" + classpath + "!");
if (classpath != null) {
String lowerCaseClasspath = classpath.toLowerCase();
int jarIndex = lowerCaseClasspath.indexOf(JYTHON_JAR);
if (jarIndex < 0) {
jarIndex = lowerCaseClasspath.indexOf(JYTHON_DEV_JAR);
}
if (jarIndex >= 0) {
int start = classpath.lastIndexOf(File.pathSeparator, jarIndex) + 1;
root = classpath.substring(start, jarIndex);
} else if (jarFileName != null) {
// in case JYTHON_JAR is referenced from a MANIFEST inside another jar on the
// classpath
root = new File(jarFileName).getParent();
}
}
}
if (root == null) {
return null;
}
does not find the root path for Jython libraries. There is a bug in this code because clause:
} else if (jarFileName != null) {
// in case JYTHON_JAR is referenced from a MANIFEST inside another jar on the
// classpath
root = new File(jarFileName).getParent();
}
}
should be placed outside if classpath != null clause (not inside) and root directory cannot be extracted from Jython jar file path as well.
Solution
But finally I found the solution and it was simple as usual.
protected PythonInterpreter(PyObject dict, PySystemState systemState, boolean useThreadLocalState) {
if (dict == null) {
dict = Py.newStringMap();
}
globals = dict;
if (systemState == null)
systemState = Py.getSystemState();
this.systemState = systemState;
setSystemState();
this.useThreadLocalState = useThreadLocalState;
if (!useThreadLocalState) {
PyModule module = new PyModule("__main__", dict);
systemState.modules.__setitem__("__main__", module);
}
if (Options.importSite) {
// Ensure site-packages are available
imp.load("site");
}
So it was enough to set
Options.importSite = false; before launching PythonInterpreter and the whole stuff related to site.py (useless in Google App Engine restricted environment) is disabled.