From: <nr...@us...> - 2010-01-23 22:58:24
|
Revision: 6962 http://jython.svn.sourceforge.net/jython/?rev=6962&view=rev Author: nriley Date: 2010-01-23 22:58:13 +0000 (Sat, 23 Jan 2010) Log Message: ----------- - optional thread locals for PythonInterpreter - fix up Javadocs for PythonInterpreter - JSR 223: fix Bindings?\194?\160changes not taking effect and leaking between threads; remove unnecessary synchronization (#1426). Thanks, emblemparade. Modified Paths: -------------- trunk/jython/src/org/python/jsr223/PyScriptEngine.java trunk/jython/src/org/python/jsr223/PyScriptEngineScope.java trunk/jython/src/org/python/util/PythonInterpreter.java trunk/jython/src/org/python/util/jython.java trunk/jython/tests/java/org/python/jsr223/ScriptEngineTest.java Modified: trunk/jython/src/org/python/jsr223/PyScriptEngine.java =================================================================== --- trunk/jython/src/org/python/jsr223/PyScriptEngine.java 2010-01-23 22:57:19 UTC (rev 6961) +++ trunk/jython/src/org/python/jsr223/PyScriptEngine.java 2010-01-23 22:58:13 UTC (rev 6962) @@ -21,12 +21,10 @@ private final PythonInterpreter interp; private final ScriptEngineFactory factory; - private final PyModule module; PyScriptEngine(ScriptEngineFactory factory) { this.factory = factory; - interp = new PythonInterpreter(new PyScriptEngineScope(this, context)); - module = (PyModule)Py.getSystemState().modules.__finditem__("__main__"); + interp = PythonInterpreter.threadLocalStateInterpreter(new PyScriptEngineScope(this, context)); } public Object eval(String script, ScriptContext context) throws ScriptException { @@ -38,6 +36,7 @@ interp.setIn(context.getReader()); interp.setOut(context.getWriter()); interp.setErr(context.getErrorWriter()); + interp.setLocals(new PyScriptEngineScope(this, context)); return interp.eval(code).__tojava__(Object.class); } catch (PyException pye) { throw scriptException(pye); @@ -93,6 +92,7 @@ public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { try { + interp.setLocals(new PyScriptEngineScope(this, context)); if (!(thiz instanceof PyObject)) { thiz = Py.java2py(thiz); } @@ -109,6 +109,7 @@ public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { try { + interp.setLocals(new PyScriptEngineScope(this, context)); PyObject function = interp.get(name); if (function == null) { throw new NoSuchMethodException(name); @@ -120,7 +121,7 @@ } public <T> T getInterface(Class<T> clazz) { - return getInterface(module, clazz); + return getInterface(new PyModule("__jsr223__", interp.getLocals()), clazz); } public <T> T getInterface(Object obj, Class<T> clazz) { @@ -130,6 +131,7 @@ if (clazz == null || !clazz.isInterface()) { throw new IllegalArgumentException("interface expected"); } + interp.setLocals(new PyScriptEngineScope(this, context)); final PyObject thiz = Py.java2py(obj); @SuppressWarnings("unchecked") T proxy = (T) Proxy.newProxyInstance( @@ -138,6 +140,7 @@ new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { + interp.setLocals(new PyScriptEngineScope(PyScriptEngine.this, context)); PyObject pyMethod = thiz.__findattr__(method.getName()); if (pyMethod == null) throw new NoSuchMethodException(method.getName()); Modified: trunk/jython/src/org/python/jsr223/PyScriptEngineScope.java =================================================================== --- trunk/jython/src/org/python/jsr223/PyScriptEngineScope.java 2010-01-23 22:57:19 UTC (rev 6961) +++ trunk/jython/src/org/python/jsr223/PyScriptEngineScope.java 2010-01-23 22:58:13 UTC (rev 6962) @@ -13,6 +13,12 @@ import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; +/** + * JSR 223 does not map well to Jython's concept of "locals" and "globals". + * Instead, SimpleScriptContext provides ENGINE_SCOPE and GLOBAL_SCOPE, each + * with its own bindings. We adapt this multi-scope object for use as both + * a local and global dictionary. + */ @ExposedType(name = "scope", isBaseType = false) public final class PyScriptEngineScope extends PyObject { public static final PyType TYPE = PyType.fromClass(PyScriptEngineScope.class); @@ -38,15 +44,13 @@ @ExposedMethod public PyObject scope_keys() { PyList members = new PyList(); - synchronized (context) { - List<Integer> scopes = context.getScopes(); - for (int scope : scopes) { - Bindings bindings = context.getBindings(scope); - if (bindings == null) - continue; - for (String key : bindings.keySet()) - members.append(new PyString(key)); - } + List<Integer> scopes = context.getScopes(); + for (int scope : scopes) { + Bindings bindings = context.getBindings(scope); + if (bindings == null) + continue; + for (String key : bindings.keySet()) + members.append(new PyString(key)); } members.sort(); return members; @@ -63,12 +67,10 @@ } public PyObject __finditem__(String key) { - synchronized (context) { - int scope = context.getAttributesScope(key); - if (scope == -1) - return null; - return Py.java2py(context.getAttribute(key, scope)); - } + int scope = context.getAttributesScope(key); + if (scope == -1) + return null; + return Py.java2py(context.getAttribute(key, scope)); } @ExposedMethod @@ -77,12 +79,10 @@ } public void __setitem__(String key, PyObject value) { - synchronized (context) { - int scope = context.getAttributesScope(key); - if (scope == -1) - scope = ScriptContext.ENGINE_SCOPE; - context.setAttribute(key, value.__tojava__(Object.class), scope); - } + int scope = context.getAttributesScope(key); + if (scope == -1) + scope = ScriptContext.ENGINE_SCOPE; + context.setAttribute(key, value.__tojava__(Object.class), scope); } @ExposedMethod @@ -91,11 +91,9 @@ } public void __delitem__(String key) { - synchronized (context) { - int scope = context.getAttributesScope(key); - if (scope == -1) - throw Py.KeyError(key); - context.removeAttribute(key, scope); - } + int scope = context.getAttributesScope(key); + if (scope == -1) + throw Py.KeyError(key); + context.removeAttribute(key, scope); } } Modified: trunk/jython/src/org/python/util/PythonInterpreter.java =================================================================== --- trunk/jython/src/org/python/util/PythonInterpreter.java 2010-01-23 22:57:19 UTC (rev 6961) +++ trunk/jython/src/org/python/util/PythonInterpreter.java 2010-01-23 22:58:13 UTC (rev 6962) @@ -22,80 +22,111 @@ import org.python.core.PyFileReader; /** - * The PythonInterpreter class is a standard wrapper for a Jython interpreter for use embedding in a - * Java application. + * The PythonInterpreter class is a standard wrapper for a Jython interpreter + * for embedding in a Java application. */ public class PythonInterpreter { - PyModule module; - + // Defaults if the interpreter uses thread-local state protected PySystemState systemState; + PyObject globals; - PyObject locals; + protected ThreadLocal<PyObject> threadLocals; protected CompilerFlags cflags = new CompilerFlags(); /** - * Initializes the jython runtime. This should only be called once, and should be called before - * any other python objects are created (including a PythonInterpreter). + * Initializes the Jython runtime. This should only be called + * once, before any other Python objects (including + * PythonInterpreter) are created. * * @param preProperties - * A set of properties. Typically System.getProperties() is used. - * PreProperties override properties from the registry file. + * A set of properties. Typically + * System.getProperties() is used. preProperties + * override properties from the registry file. * @param postProperties - * An other set of properties. Values like python.home, python.path and all other - * values from the registry files can be added to this property set. PostProperties - * will override system properties and registry properties. + * Another set of properties. Values like python.home, + * python.path and all other values from the registry + * files can be added to this property + * set. postProperties override system properties and + * registry properties. * @param argv - * Command line argument. These values will assigned to sys.argv. + * Command line arguments, assigned to sys.argv. */ public static void initialize(Properties preProperties, Properties postProperties, String[] argv) { PySystemState.initialize(preProperties, postProperties, argv); } /** - * Create a new Interpreter with an empty dictionary + * Creates a new interpreter with an empty local namespace. */ public PythonInterpreter() { this(null, null); } /** - * Create a new interpreter with the given dictionary to use as its namespace + * Creates a new interpreter with the ability to maintain a + * separate local namespace for each thread (set by invoking + * setLocals()). + * + * @param dict + * a Python mapping object (e.g., a dictionary) for use + * as the default namespace */ + public static PythonInterpreter threadLocalStateInterpreter(PyObject dict) { + return new PythonInterpreter(dict, null, true); + } + + /** + * Creates a new interpreter with a specified local namespace. + * + * @param dict + * a Python mapping object (e.g., a dictionary) for use + * as the namespace + */ public PythonInterpreter(PyObject dict) { this(dict, null); } public PythonInterpreter(PyObject dict, PySystemState systemState) { + this(dict, systemState, false); + } + + protected PythonInterpreter(PyObject dict, PySystemState systemState, boolean useThreadLocalState) { if (dict == null) { dict = new PyStringMap(); } - if (systemState == null) { + globals = dict; + + if (systemState == null) systemState = Py.getSystemState(); - if (systemState == null) { - systemState = new PySystemState(); - } + this.systemState = systemState; + setSystemState(); + + if (useThreadLocalState) { + threadLocals = new ThreadLocal<PyObject>(); + } else { + PyModule module = new PyModule("__main__", dict); + systemState.modules.__setitem__("__main__", module); } - this.systemState = systemState; - setState(); - module = new PyModule("__main__", dict); - systemState.modules.__setitem__("__main__", module); - locals = dict; } - protected void setState() { - Py.setSystemState(systemState); + public PySystemState getSystemState() { + return systemState; } + protected void setSystemState() { + Py.setSystemState(getSystemState()); + } + /** - * Set the Python object to use for the standard input stream + * Sets a Python object to use for the standard input stream. * * @param inStream - * Python file-like object to use as input stream + * a Python file-like object to use as input stream */ public void setIn(PyObject inStream) { - systemState.stdin = inStream; + getSystemState().stdin = inStream; } public void setIn(java.io.Reader inStream) { @@ -103,7 +134,8 @@ } /** - * Set a java.io.InputStream to use for the standard input stream + * Sets a java.io.InputStream to use for the standard input + * stream. * * @param inStream * InputStream to use as input stream @@ -113,13 +145,13 @@ } /** - * Set the Python object to use for the standard output stream + * Sets a Python object to use for the standard output stream. * * @param outStream * Python file-like object to use as output stream */ public void setOut(PyObject outStream) { - systemState.stdout = outStream; + getSystemState().stdout = outStream; } public void setOut(java.io.Writer outStream) { @@ -127,7 +159,8 @@ } /** - * Set a java.io.OutputStream to use for the standard output stream + * Sets a java.io.OutputStream to use for the standard output + * stream. * * @param outStream * OutputStream to use as output stream @@ -137,7 +170,7 @@ } public void setErr(PyObject outStream) { - systemState.stderr = outStream; + getSystemState().stderr = outStream; } public void setErr(java.io.Writer outStream) { @@ -149,44 +182,46 @@ } /** - * Evaluate a string as Python source and return the result + * Evaluates a string as a Python expression and returns the + * result. */ public PyObject eval(String s) { - setState(); - return __builtin__.eval(new PyString(s), locals); + setSystemState(); + return __builtin__.eval(new PyString(s), getLocals()); } /** - * Evaluate a Python code object and return the result + * Evaluates a Python code object and returns the result. */ public PyObject eval(PyObject code) { - setState(); - return __builtin__.eval(code, locals, locals); + setSystemState(); + return __builtin__.eval(code, getLocals()); } /** - * Execute a string of Python source in the local namespace + * Executes a string of Python source in the local namespace. */ public void exec(String s) { - setState(); - Py.exec(Py.compile_flags(s, "<string>", CompileMode.exec, cflags), locals, locals); + setSystemState(); + Py.exec(Py.compile_flags(s, "<string>", CompileMode.exec, cflags), getLocals(), null); Py.flushLine(); } /** - * Execute a Python code object in the local namespace + * Executes a Python code object in the local namespace. */ public void exec(PyObject code) { - setState(); - Py.exec(code, locals, locals); + setSystemState(); + Py.exec(code, getLocals(), null); Py.flushLine(); } /** - * Execute a file of Python source in the local namespace + * Executes a file of Python source in the local namespace. */ public void execfile(String filename) { - setState(); + PyObject locals = getLocals(); + setSystemState(); __builtin__.execfile_flags(filename, locals, locals, cflags); Py.flushLine(); } @@ -196,17 +231,20 @@ } public void execfile(java.io.InputStream s, String name) { - setState(); - Py.runCode(Py.compile_flags(s, name, CompileMode.exec, cflags), locals, locals); + setSystemState(); + Py.runCode(Py.compile_flags(s, name, CompileMode.exec, cflags), null, getLocals()); Py.flushLine(); } /** - * Compile a string of Python source as either an expression (if possible) or module. + * Compiles a string of Python source as either an expression (if + * possible) or a module. * - * Designed for use by a JSR 223 implementation: "the Scripting API does not distinguish - * between scripts which return values and those which do not, nor do they make the - * corresponding distinction between evaluating or executing objects." (SCR.4.2.1) + * Designed for use by a JSR 223 implementation: "the Scripting + * API does not distinguish between scripts which return values + * and those which do not, nor do they make the corresponding + * distinction between evaluating or executing objects." + * (SCR.4.2.1) */ public PyCode compile(String script) { return compile(script, "<script>"); @@ -219,68 +257,81 @@ } public PyCode compile(Reader reader, String filename) { mod node = ParserFacade.parseExpressionOrModule(reader, filename, cflags); - setState(); + setSystemState(); return Py.compile_flags(node, filename, CompileMode.eval, cflags); } public PyObject getLocals() { - return locals; + if (threadLocals == null) + return globals; + + PyObject locals = threadLocals.get(); + if (locals != null) + return locals; + return globals; } public void setLocals(PyObject d) { - locals = d; + if (threadLocals == null) + globals = d; + else + threadLocals.set(d); } /** - * Set a variable in the local namespace + * Sets a variable in the local namespace. * * @param name * the name of the variable * @param value - * the value to set the variable to. Will be automatically converted to an - * appropriate Python object. + * the object to set the variable to (as converted to + * an appropriate Python object) */ public void set(String name, Object value) { - locals.__setitem__(name.intern(), Py.java2py(value)); + getLocals().__setitem__(name.intern(), Py.java2py(value)); } /** - * Set a variable in the local namespace + * Sets a variable in the local namespace. * * @param name * the name of the variable * @param value - * the value to set the variable to + * the Python object to set the variable to */ public void set(String name, PyObject value) { - locals.__setitem__(name.intern(), value); + getLocals().__setitem__(name.intern(), value); } /** - * Get the value of a variable in the local namespace + * Returns the value of a variable in the local namespace. * * @param name * the name of the variable - * @return the value of the variable, or null if that name isn't assigned + * @return the value of the variable, or null if that name isn't + * assigned */ public PyObject get(String name) { - return locals.__finditem__(name.intern()); + return getLocals().__finditem__(name.intern()); } /** - * Get the value of a variable in the local namespace Value will be returned as an instance of - * the given Java class. <code>interp.get("foo", Object.class)</code> will return the most + * Returns the value of a variable in the local namespace. + * + * The value will be returned as an instance of the given Java class. + * <code>interp.get("foo", Object.class)</code> will return the most * appropriate generic Java object. * * @param name * the name of the variable * @param javaclass * the class of object to return - * @return the value of the variable as the given class, or null if that name isn't assigned + * @return the value of the variable as the given class, or null + * if that name isn't assigned */ public <T> T get(String name, Class<T> javaclass) { - PyObject val = locals.__finditem__(name.intern()); + PyObject val = getLocals().__finditem__(name.intern()); if (val == null) { return null; } @@ -288,7 +339,8 @@ } public void cleanup() { - systemState.callExitFunc(); + setSystemState(); + Py.getSystemState().callExitFunc(); try { Py.getSystemState().stdout.invoke("flush"); } catch (PyException pye) { Modified: trunk/jython/src/org/python/util/jython.java =================================================================== --- trunk/jython/src/org/python/util/jython.java 2010-01-23 22:57:19 UTC (rev 6961) +++ trunk/jython/src/org/python/util/jython.java 2010-01-23 22:58:13 UTC (rev 6962) @@ -221,15 +221,15 @@ } } else if (opts.filename.equals("-")) { try { - interp.locals.__setitem__(new PyString("__file__"), new PyString("<stdin>")); + interp.globals.__setitem__(new PyString("__file__"), new PyString("<stdin>")); interp.execfile(System.in, "<stdin>"); } catch (Throwable t) { Py.printException(t); } } else { try { - interp.locals.__setitem__(new PyString("__file__"), - new PyString(opts.filename)); + interp.globals.__setitem__(new PyString("__file__"), + new PyString(opts.filename)); FileInputStream file; try { Modified: trunk/jython/tests/java/org/python/jsr223/ScriptEngineTest.java =================================================================== --- trunk/jython/tests/java/org/python/jsr223/ScriptEngineTest.java 2010-01-23 22:57:19 UTC (rev 6961) +++ trunk/jython/tests/java/org/python/jsr223/ScriptEngineTest.java 2010-01-23 22:58:13 UTC (rev 6962) @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.StringReader; +import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.Invocable; @@ -102,6 +103,46 @@ assertNull(pythonEngine.get("x")); } + class ThreadLocalBindingsTest implements Runnable { + ScriptEngine engine; + Object x; + Throwable exception; + + public ThreadLocalBindingsTest(ScriptEngine engine) { + this.engine = engine; + } + + public void run() { + try { + Bindings bindings = engine.createBindings(); + assertNull(engine.eval("try: a\nexcept NameError: pass\nelse: raise Exception('a is defined', a)", bindings)); + bindings.put("x", -7); + x = engine.eval("x", bindings); + } catch (Throwable e) { + e.printStackTrace(); + exception = e; + } + } + } + + public void testThreadLocalBindings() throws ScriptException, InterruptedException { + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine pythonEngine = manager.getEngineByName("python"); + + pythonEngine.put("a", 42); + pythonEngine.put("x", 15); + + ThreadLocalBindingsTest test = new ThreadLocalBindingsTest(pythonEngine); + Thread thread = new Thread(test); + thread.run(); + thread.join(); + assertNull(test.exception); + assertEquals(Integer.valueOf(-7), test.x); + assertEquals(Integer.valueOf(15), pythonEngine.get("x")); + assertNull(pythonEngine.eval("del x")); + assertNull(pythonEngine.get("x")); + } + public void testInvoke() throws ScriptException, NoSuchMethodException { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine pythonEngine = manager.getEngineByName("python"); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |