[Jython-checkins] jython: Adds bytecode notification mechanism
jim.baker
jython-checkins at python.org
Tue Jan 20 00:09:22 CET 2015
https://hg.python.org/jython/rev/88209128cde8
changeset: 7542:88209128cde8
user: Jiwon Seo <jiwon at stanford.edu>
date: Mon Jan 19 16:08:45 2015 -0700
summary:
Adds bytecode notification mechanism
Callbacks can be registered/unregistered to be notified when bytecode
is loaded, using API in jythonlib.bytecodetools.
Merged from https://bitbucket.org/jython/jython/pull-request/41/adding-bytecode-notification-mechanism/
with minor changes by Jim Baker.
files:
ACKNOWLEDGMENTS | 1 +
Lib/jythonlib.py | 1 +
Lib/test/test_bytecodetools_jy.py | 107 ++++++++++
NEWS | 2 +
src/org/python/compiler/LegacyCompiler.java | 5 -
src/org/python/core/BytecodeLoader.java | 4 +-
src/org/python/core/BytecodeNotification.java | 81 +++++++
src/org/python/core/CompilerFacade.java | 1 -
src/org/python/core/PythonCodeBundle.java | 2 -
src/org/python/modules/Setup.java | 1 +
src/org/python/modules/_bytecodetools.java | 66 ++++++
11 files changed, 262 insertions(+), 9 deletions(-)
diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS
--- a/ACKNOWLEDGMENTS
+++ b/ACKNOWLEDGMENTS
@@ -146,6 +146,7 @@
Yuji Yamano
Pekka Klärck
Nathaniel Kenmir
+ Jiwon Seo
Local Variables:
mode: indented-text
diff --git a/Lib/jythonlib.py b/Lib/jythonlib.py
--- a/Lib/jythonlib.py
+++ b/Lib/jythonlib.py
@@ -1,4 +1,5 @@
from _jythonlib import *
+import _bytecodetools as bytecodetools
# Convenience imports, since this is the most common case for using
# jythonlib, especially with MapMaker
diff --git a/Lib/test/test_bytecodetools_jy.py b/Lib/test/test_bytecodetools_jy.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_bytecodetools_jy.py
@@ -0,0 +1,107 @@
+from test import test_support
+from jythonlib import bytecodetools as tools
+from java.util.concurrent import Callable
+from org.python.core import Options
+
+import glob
+import os.path
+import sys
+import tempfile
+import unittest
+
+
+class BytecodeCallbackTest(unittest.TestCase):
+
+ def setUp(self):
+ self.count=0
+ tools.clear()
+
+ def assert_callback(self, name, byte, klass):
+ self.count+=1
+ self.assertEqual(name, klass.name)
+
+ def test_register(self):
+ tools.register(self.assert_callback)
+ eval("42+1")
+
+ def test_unregister(self):
+ self.count=0
+ tools.register(self.assert_callback)
+ eval("42+1")
+ self.assertEqual(self.count, 1)
+
+ tools.unregister(self.assert_callback)
+ eval("42+1")
+ self.assertEqual(self.count, 1)
+
+ def faulty_callback(self, name, byte, klass):
+ raise Exception("test exception")
+ def faulty_callback2(self, name, byte, klass, bogus):
+ return
+
+ def test_faulty_callback(self):
+ import java.lang.System as Sys
+ import java.io.PrintStream as PrintStream
+ import java.io.OutputStream as OutputStream
+
+ class NullOutputStream(OutputStream):
+ def write(self, b): pass
+ def write(self, buf, offset, len): pass
+
+ syserr = Sys.err
+ Sys.setErr(PrintStream(NullOutputStream()))
+
+ tools.register(self.faulty_callback)
+ tools.register(self.assert_callback)
+ tools.register(self.faulty_callback2)
+ self.count=0
+ try:
+ eval("42+1")
+ finally:
+ self.assertTrue(tools.unregister(self.faulty_callback))
+ self.assertTrue(tools.unregister(self.faulty_callback2))
+ self.assertTrue(tools.unregister(self.assert_callback))
+ Sys.setErr(syserr)
+ self.assertEqual(self.count, 1)
+
+
+class ProxyDebugDirectoryTest(unittest.TestCase):
+ """ProxyDebugDirectory used to be the only way to save proxied classes"""
+
+ def setUp(self):
+ self.tmpdir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ test_support.rmtree(self.tmpdir)
+
+ def test_set_debug_directory(self):
+ """Verify that proxy debug directory can be set at runtime"""
+ self.assertIs(Options.proxyDebugDirectory, None)
+ Options.proxyDebugDirectory = self.tmpdir
+
+ class C(Callable):
+ def call(self):
+ return 47
+
+ self.assertEqual(C().call(), 47)
+ proxy_dir = os.path.join(self.tmpdir, "org", "python", "proxies")
+ # If test script is run outside of regrtest, the first path is used;
+ # otherwise the second
+ proxy_classes = glob.glob(os.path.join(proxy_dir, "*.class")) + \
+ glob.glob(os.path.join(proxy_dir, "test", "*.class"))
+ self.assertEqual(len(proxy_classes), 1, "Only one proxy class is generated")
+ self.assertRegexpMatches(
+ proxy_classes[0],
+ r'\$C\$\d+.class$')
+
+
+def test_main():
+ test_support.run_unittest(
+ BytecodeCallbackTest,
+ ProxyDebugDirectoryTest
+ )
+
+
+if __name__ == '__main__':
+ test_main()
+
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -52,6 +52,8 @@
- CPython's _json.c was ported to Java to speed up JSON encoding/decoding
- Upgraded third party libraries
- Initial support for ensurepip module
+ - Callbacks can be registered/unregistered to be notified when
+ bytecode is loaded, using jythonlib.bytecodetools
Potentially backwards breaking changes, removing silent errors:
diff --git a/src/org/python/compiler/LegacyCompiler.java b/src/org/python/compiler/LegacyCompiler.java
--- a/src/org/python/compiler/LegacyCompiler.java
+++ b/src/org/python/compiler/LegacyCompiler.java
@@ -53,11 +53,6 @@
}
}
- public void saveCode(String directory) throws Exception {
- // FIXME: this is slightly broken, it should use the directory
- Py.saveClassFile(name, ostream());
- }
-
private ByteArrayOutputStream ostream() throws Exception {
if (ostream == null) {
ostream = new ByteArrayOutputStream();
diff --git a/src/org/python/core/BytecodeLoader.java b/src/org/python/core/BytecodeLoader.java
--- a/src/org/python/core/BytecodeLoader.java
+++ b/src/org/python/core/BytecodeLoader.java
@@ -34,7 +34,9 @@
} catch (SecurityException e) {
}
}
- return loader.loadClassFromBytes(name, data);
+ Class<?> c = loader.loadClassFromBytes(name, data);
+ BytecodeNotification.notify(name, data, c);
+ return c;
}
/**
diff --git a/src/org/python/core/BytecodeNotification.java b/src/org/python/core/BytecodeNotification.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/BytecodeNotification.java
@@ -0,0 +1,81 @@
+package org.python.core;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.Collections;
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Notifies registered callbacks if new bytecode is loaded.
+ */
+public class BytecodeNotification {
+ /**
+ * Interface for callbacks.
+ * Notifies the name of the loaded class, raw bytes of the class,
+ * and the Java class object.
+ */
+ public interface Callback {
+ public void notify(String name, byte[] bytes, Class c);
+ }
+
+ /**
+ * The following list stores register callback objects.
+ * The list is shared among the PySystemState objects
+ * if there are multiple instances.
+ */
+ private static List<Callback> callbacks = new CopyOnWriteArrayList();
+
+ static {
+ // Maintain legacy behavior
+ register(new Callback() {
+ public void notify(String name, byte[] bytes, Class c) {
+ if (Options.proxyDebugDirectory == null ||
+ (!name.startsWith("org.python.pycode.") &&
+ !name.startsWith("org.python.proxies."))) {
+ return;
+ }
+ ByteArrayOutputStream ostream = new ByteArrayOutputStream(bytes.length);
+ ostream.write(bytes, 0, bytes.length);
+ Py.saveClassFile(name, ostream);
+ }
+ });
+ }
+
+ /**
+ * Registers the class as a callback
+ *
+ * @param n the callback object
+ */
+ public static void register(Callback n) { callbacks.add(n); }
+
+ /**
+ * Unregisters the callback object
+ *
+ * @param n the callback object
+ * @return true if successfully removed and
+ * false if the callback object was not registered
+ */
+ public static boolean unregister(Callback n) { return callbacks.remove(n); }
+
+ /**
+ * Clears all the registered callbacks
+ */
+ public static void clear() { callbacks.clear(); }
+
+ /**
+ * Notifies that the new bytecode to the registered callbacks
+ *
+ * @param name the name of the class of the new bytecode
+ * @param data raw byte data of the class
+ * @param class Java class object of the new bytecode
+ */
+ public static void notify(String name, byte[] data, Class klass) {
+ for (Callback c:callbacks) {
+ try {
+ c.notify(name, data, klass);
+ } catch (Exception e) {
+ Py.writeWarning("BytecodeNotification", "Exception from callback:"+e);
+ }
+ }
+ }
+}
diff --git a/src/org/python/core/CompilerFacade.java b/src/org/python/core/CompilerFacade.java
--- a/src/org/python/core/CompilerFacade.java
+++ b/src/org/python/core/CompilerFacade.java
@@ -30,7 +30,6 @@
try {
PythonCodeBundle bundle = compiler.compile(node, name, filename,
linenumbers, printResults, cflags);
- bundle.saveCode(Options.proxyDebugDirectory);
return bundle.loadCode();
} catch (Throwable t) {
throw ParserFacade.fixParseError(null, t, filename);
diff --git a/src/org/python/core/PythonCodeBundle.java b/src/org/python/core/PythonCodeBundle.java
--- a/src/org/python/core/PythonCodeBundle.java
+++ b/src/org/python/core/PythonCodeBundle.java
@@ -8,6 +8,4 @@
void writeTo(OutputStream stream) throws Exception;
- void saveCode(String directory) throws Exception;
-
}
diff --git a/src/org/python/modules/Setup.java b/src/org/python/modules/Setup.java
--- a/src/org/python/modules/Setup.java
+++ b/src/org/python/modules/Setup.java
@@ -28,6 +28,7 @@
public static String[] builtinModules = {
"_ast:org.python.antlr.ast.AstModule",
+ "_bytecodetools",
"_codecs",
"_collections:org.python.modules._collections.Collections",
"_csv:org.python.modules._csv._csv",
diff --git a/src/org/python/modules/_bytecodetools.java b/src/org/python/modules/_bytecodetools.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/modules/_bytecodetools.java
@@ -0,0 +1,66 @@
+package org.python.modules;
+
+import org.python.core.BytecodeNotification;
+import org.python.core.PyObject;
+import org.python.core.Py;
+
+/**
+ * BytecodeTools provides tools for generated JVM bytecode.
+ * <p/>
+ * This module supports registering a python callback function
+ * to be notified when new bytecode is loaded.
+ * see also core/BytecodeNotification.java
+ */
+public class _bytecodetools {
+ public static final String __doc__ =
+ "Provides utilities for generated bytecode.\n";
+
+ public static final String __name__ = "BytecodeTools";
+
+ static class _Callback implements BytecodeNotification.Callback {
+ PyObject callback;
+
+ public _Callback(PyObject callback) {
+ this.callback = callback;
+ }
+
+ public void notify(String name, byte[] bytes, Class c) {
+ callback.__call__(Py.java2py(name), Py.java2py(bytes), Py.java2py(c));
+ }
+
+ public int hashCode() {
+ return callback.hashCode();
+ }
+
+ public boolean equals(Object other) {
+ if (!(other instanceof _Callback)) return false;
+ _Callback that = (_Callback) other;
+ return callback.equals(that.callback);
+ }
+ }
+
+ /**
+ * Registers a python callback function that will be notified on bytecode loading.
+ *
+ * @param callback a Python callback function
+ */
+ public static void register(final PyObject callback) {
+ BytecodeNotification.register(new _Callback(callback));
+ }
+
+ /**
+ * Unregisters a python callback function.
+ *
+ * @param callback a Python callback function
+ */
+ public static boolean unregister(final PyObject callback) {
+ return BytecodeNotification.unregister(new _Callback(callback));
+ }
+
+ /**
+ * Clears all the registered callbacks.
+ */
+ public static void clear() {
+ BytecodeNotification.clear();
+ }
+}
--
Repository URL: https://hg.python.org/jython
More information about the Jython-checkins
mailing list