Hey,

Andi, thanks for the reply - I've created a github mirror of the pylucene
project for our own use (which I intend to keep synced with your SVN
repository as its official upstream), located at:

https://github.com/lskillen/pylucene

As suggested I have formatted (and attached) a patch of the unfinished code
that we're using for the through-layer exceptions.  Alternatively the diff
can be inspected via github by diff'ing between the new
feature-thru-exception branch that I have created and the master branch, as
such:

https://github.com/lskillen/pylucene/compare/feature-thru-exception?expand=1

Although we've run the test suite without issues I realise there may still
be functionality/style/logical issues with the code.  I also suspect that
there may not be specific test cases that target regression failures for
exceptions (yet), so confidence isn't high!  All comments are welcome and I
realise this will likely require further changes and a repeatable test case
before it is acceptable, but that's fine.

Thanks,
Lee



On 4 July 2014 18:17, Andi Vajda <va...@apache.org> wrote:

>
> On Jul 4, 2014, at 18:33, Lee Skillen <lskil...@vulcanft.com> wrote:
>
> Hi Andi,
>
> First of all, absolutely fantastic work on the JCC project, we love it -
> Second of all, apologies for contacting you out of the blue via email, but
> I wasn't sure of the best place to contact you to ask a few questions
> (technical-level, not support-level).  If it isn't acceptable to email
> please let me know and I can move the discussion somewhere else.
>
>
> Yes, please include pylucene-dev@lucene.apache.org (subscription
> required. send mail pylucene-dev-subscribe@ and follow the instructions
> in the reply).
>
> A bit of background first of all :-
>
> We've been integrating JCC for the past two weeks on a sizeable
> Python/Java/C++ project (where Python is a controller/glue layer) having
> migrated from a previous solution to integrate across the languages, and
> for the most part JCC has been a lifesaver.
>
> Technically we've only hit one major issue so far and that has been the
> translation of exceptions through the layers.  Our Java application does a
> lot of double-dispatch (visitation) type of execution and we're calling
> outwards to Python to implement the concrete visitation logic.
>
> In the case of an exception being thrown it results in JCC collating a
> traceback and captured exception at every level of scope, resulting in a
> giant JavaError exception which has lost it's original PythonException
> context.
>
> I was actually able to fix this by attempting to capture the full context
> via PyErr_Fetch() when the first python object is thrown and storing it
> within PythonException, and then ensuring that this is carried through each
> layer appropriately - This seems to work very well, although admittedly I
> haven't considered regressive errors or compatibility yet, but just wanted
> to proof-of-concept it first of all then speak with a JCC maintainer.
>
> The net result is being able to catch errors in a pipeline similar to the
> following:
>
> Python
>    -> Java
>        -> Python
>          -> Raise MyException
>        <- Throw PythonException (containing MyException)
>    <- Inject MyException
> Catch MyException
>
> My main questions are to ask your thoughts about :-
>
> - What are your thoughts about through-layer exceptions and a feature
> improvement like this?
>
>
> I agree that losing information during exception throwing is not good. If
> you've got an improvement in the form of a patch, do not hesitate in
> sending it in.
>
> - JCC is (I think) currently at home with pylucene, are there are plans to
> making it separate?
>
>
> No such plans at the moment.
>
> - Are you happy if I create a public github project and add the patched
> code in there for review?
>
>
> You're welcome to fork the code if you'd like but you don't have to if all
> you want is sending a patch. Your call.
>
> Thank you for the kind words !
>
> Andi..
>
>
> Hope to hear from you!
>
> Cheers,
> Lee
>
>
>
> TL;DR;
>
> We would like to :-
>
> - Improve JCC's through-layer exception support.
> - Establish a github project for JCC alone.
>
> --
> Lee Skillen
>
> Vulcan Financial Technologies
> 1st Floor, 47 Malone Road, Belfast, BT9 6RY
>
> Office:  +44 (0)28 95 817888
> Mobile:  +44 (0)78 41 425152
> Web:     www.vulcanft.com
>
>


-- 
Lee Skillen

Vulcan Financial Technologies
1st Floor, 47 Malone Road, Belfast, BT9 6RY

Office:  +44 (0)28 95 817888
Mobile:  +44 (0)78 41 425152
Web:     www.vulcanft.com
From 935d6c36fa8a547c1f0161342a2fa8bfe10be735 Mon Sep 17 00:00:00 2001
From: Lee Skillen <lskil...@vulcanft.com>
Date: Tue, 8 Jul 2014 18:14:46 +0100
Subject: [PATCH] Improve support for through-layer exceptions in which an
 exception thrown within the PythonVM will be transparently carried through
 the JavaVM and can be recaught within Python again.
Organization: Vulcan Financial Technologies

Signed-off-by: Lee Skillen <lskil...@vulcanft.com>
---
 jcc/java/org/apache/jcc/PythonException.java |  1 +
 jcc/jcc/sources/functions.cpp                | 54 ++++++++++++++++++++++------
 jcc/jcc/sources/jcc.cpp                      | 22 ++++++++++++
 3 files changed, 66 insertions(+), 11 deletions(-)

diff --git a/jcc/java/org/apache/jcc/PythonException.java b/jcc/java/org/apache/jcc/PythonException.java
index 4eae86b..454240e 100644
--- a/jcc/java/org/apache/jcc/PythonException.java
+++ b/jcc/java/org/apache/jcc/PythonException.java
@@ -19,6 +19,7 @@ package org.apache.jcc;
 public class PythonException extends RuntimeException {
     public boolean withTrace = true;
     protected String message, errorName, traceback;
+    protected Object exc, value, tb;
 
     public PythonException(String message)
     {
diff --git a/jcc/jcc/sources/functions.cpp b/jcc/jcc/sources/functions.cpp
index 7a85fdf..6211988 100644
--- a/jcc/jcc/sources/functions.cpp
+++ b/jcc/jcc/sources/functions.cpp
@@ -787,7 +787,7 @@ int _parseArgs(PyObject **args, unsigned int count, char *types, ...)
                       }
                       else
                           break;
-                  } 
+                  }
 
                   if (last && (arg == Py_None ||
                                PyString_Check(arg) || PyUnicode_Check(arg)))
@@ -1354,15 +1354,44 @@ PyObject *PyErr_SetArgsError(PyTypeObject *type, char *name, PyObject *args)
 PyObject *PyErr_SetJavaError()
 {
     JNIEnv *vm_env = env->get_vm_env();
-    jthrowable throwable = vm_env->ExceptionOccurred();
-    PyObject *err;
+    jthrowable obj = vm_env->ExceptionOccurred();
 
+#if defined(PYTHON)
+    jclass pycls = env->getPythonExceptionClass();
+
+    /*
+     * Support through-layer exceptions by taking the active PythonException and
+     * making the enclosed exception visible to Python again.
+     */
+    if (vm_env->IsSameObject(vm_env->GetObjectClass(obj), pycls))
+    {
+        /* Retrieve the store exception from PythonException */
+        jfieldID exc_fid = vm_env->GetFieldID(pycls, "exc", "Ljava/lang/Object;");
+        jfieldID value_fid = vm_env->GetFieldID(pycls, "value", "Ljava/lang/Object;");
+        jfieldID tb_fid = vm_env->GetFieldID(pycls, "tb", "Ljava/lang/Object;");
+
+        PyObject* exc = (PyObject*) env->longValue(vm_env->GetObjectField(obj, exc_fid));
+        PyObject* value = (PyObject*) env->longValue(vm_env->GetObjectField(obj, value_fid));
+        PyObject* tb = (PyObject*) env->longValue(vm_env->GetObjectField(obj, tb_fid));
+
+        /* Make the exception visible to Python */
+        PyErr_Restore(exc, value, tb);
+        Py_XDECREF(exc);
+        Py_XDECREF(value);
+        Py_XDECREF(tb);
+        return NULL;
+    }
+#endif
+
+    /*
+     * Expose the Java exception to Python as a JavaError.
+     */
+    PyObject *err;
     vm_env->ExceptionClear();
-    err = t_Throwable::wrap_Object(Throwable(throwable));
+    err = t_Throwable::wrap_Object(Throwable(obj));
 
     PyErr_SetObject(PyExc_JavaError, err);
     Py_DECREF(err);
-
     return NULL;
 }
 
@@ -1411,17 +1440,20 @@ void throwPythonError(void)
         return;
     }
 
+    jclass pycls = env->getPythonExceptionClass();
+
     if (exc)
     {
-        PyObject *name = PyObject_GetAttrString(exc, "__name__");
+        if (!env->get_vm_env()->ExceptionOccurred())
+        {
+            PyObject *name = PyObject_GetAttrString(exc, "__name__");
 
-        env->get_vm_env()->ThrowNew(env->getPythonExceptionClass(),
-                                    PyString_AS_STRING(name));
-        Py_DECREF(name);
+            env->get_vm_env()->ThrowNew(pycls, PyString_AS_STRING(name));
+            Py_DECREF(name);
+        }
     }
     else
-        env->get_vm_env()->ThrowNew(env->getPythonExceptionClass(),
-                                    "python error");
+        env->get_vm_env()->ThrowNew(pycls, "python error");
 }
 
 void throwTypeError(const char *name, PyObject *object)
diff --git a/jcc/jcc/sources/jcc.cpp b/jcc/jcc/sources/jcc.cpp
index 0062bb5..620a309 100644
--- a/jcc/jcc/sources/jcc.cpp
+++ b/jcc/jcc/sources/jcc.cpp
@@ -761,6 +761,25 @@ static void JNICALL _PythonException_getErrorInfo(JNIEnv *vm_env, jobject self)
     jclass jcls = vm_env->GetObjectClass(self);
 
     PyErr_Fetch(&type, &value, &tb);
+    PyErr_NormalizeException(&type, &value, &tb);
+
+    if (value != NULL && tb != NULL)
+    {
+        /* PyErr_NormalizeException does not implicitly set the __traceback__
+         * attribute on the exception value, so it needs to be set explicitly.
+         * See: https://docs.python.org/3/c-api/exceptions.html
+         */
+        PyException_SetTraceback(value, tb);
+    }
+
+    /* Store the exception within the PythonException java class */
+    jfieldID exc_fid = vm_env->GetFieldID(jcls, "exc", "Ljava/lang/Object;");
+    jfieldID value_fid = vm_env->GetFieldID(jcls, "value", "Ljava/lang/Object;");
+    jfieldID tb_fid = vm_env->GetFieldID(jcls, "tb", "Ljava/lang/Object;");
+
+    vm_env->SetObjectField(self, exc_fid, env->boxLong((jlong) type));
+    vm_env->SetObjectField(self, value_fid, env->boxLong((jlong) value));
+    vm_env->SetObjectField(self, tb_fid, env->boxLong((jlong) tb));
 
     errorName = PyObject_GetAttrString(type, "__name__");
     if (errorName != NULL)
@@ -790,6 +809,8 @@ static void JNICALL _PythonException_getErrorInfo(JNIEnv *vm_env, jobject self)
         }
     }
 
+#if 0
+    // FIXME: Still capture the full traceback?
     PyObject *module = NULL, *cls = NULL, *stringIO = NULL, *result = NULL;
     PyObject *_stderr = PySys_GetObject("stderr");
     if (!_stderr)
@@ -831,6 +852,7 @@ static void JNICALL _PythonException_getErrorInfo(JNIEnv *vm_env, jobject self)
 
     PySys_SetObject("stderr", _stderr);
     Py_DECREF(_stderr);
+#endif /* 0 */
 
     return;
 
-- 
1.9.0.dirty

Reply via email to