[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