Attach PyObject to exceptions, fix memory leak when constructing Java StackTraceElements#634
Attach PyObject to exceptions, fix memory leak when constructing Java StackTraceElements#634magicmq wants to merge 3 commits into
StackTraceElements#634Conversation
I have started looking at this and overall it looks good so far. I will need more time to fully review it but for now I keep coming back to this point. Your documentation and explanation here make me feel like I could comfortably use the API correctly but I don't understand why we need to structure it that way. Is there a technical or compatibility reason why a Python Exception that was originally a Java Exception can't be a PythonException instead of a JepException? I think we have a python exception object and it might not be as interesting as the original java cause but it should still technically be enough to satisfy the requirements to make a PythonException.
Awesome! |
I didn't necessarily make it that way because of a technical or compatibility issue, but a Python exception that was originally a Java exception wouldn't have a Python exception object, would it? I know JEP converts a few select Java exceptions into equivalent Python exceptions (and in those cases, Python exception objects would be available), but my understanding was that this was done solely to facilitate catching those Java exceptions in Python, not necessarily because the Python exception object adds any value or additional information to the exception itself. I suppose that a |
In In fact, I just realized that with this new code, jpyException will always be a Java PyObject wrapping that python exception, even when the exception originated in java. For the case where you currently create a JepException we create jpyException without ever using it.
Even in the case where there is no equivalent Python exception we create a Python RuntimeError so the lack of an equivalent exception type doesn't prevent the creation of a Python exception. I agree that the original intent was to make it easier to catch Java Exceptions in Python and there isn't much value in the Python Exception. But it seems to me this feature proves there is some value in the Python exception. The Python traceback is an attribute of a Python exception, you want access to this traceback even when an exception originated in Java so in this use case there is some value in the Python exception even if it originated from a Java exception. You find the formatted traceback string to be useful but I can imagine there might be use cases where the traceback objects themselves might be useful for similar reasons. I see including the whole PyObject in the PythonException as a convenient way to just include all the context we have around the exception from Python in case anyone catching it wants to use it. I don't see any reason to ever include the formatted traceback and not give access to the whole exception.
This seems less confusing to me. If there was ever a Python exception then I think Jep should throw PythonException, even in the case where that PythonException is just triggered by a Java exception. I think this is similar to when a Java Exception has a long chain of CausedBy, often this is confusing and some of those layers tend to be meaningless but trying to cut out layers of causes from java exception is only going to make it more confusing. |
Changes
The below changes are non-breaking.
Minor changes to exception handling
This PR changes exception handling within an interpreter context, for the following scenarios:
ValueError,SyntaxError,ArithmeticError, etc.), aPythonExceptionobject is instantiated and thrown (as long as the exception isn't caught in Python). The underlying Python exception object is attached to the exception (as aPyObject) and is obtainable viaPythonException.getPythonException(). For example:JepExceptionobject is instantiated and thrown. For example:The following changes were also made:
getPythonTraceback()method was added toJepException: a traceback may be available for both of the above scenarios, while a Python exception object will only be available forPythonException.process_python_tracebackfunction was added tojep_exceptions.cto provide the formatted Python traceback String and the frames for subsequent processing into JavaStackTraceElements.TestExceptionCausetest was merged into a new test,TestJepException, which performs more granular testing onJepException(andPythonException) in a variety of possible scenarios.Commit 4e547e9 makes the above changes/additions.
Quick Aside
I think the above changes could potentially create confusion, particularly with the second scenario: users may expect all exceptions that "came from" Python code to be a
PythonException, even if the exception originated in Java. I can't think of a better way to solve this without making breaking changes to the existing code, but nevertheless, I attempted to make this as clear as possible in the JavaDocs.Although this PR solves the issues outlined in #319 and #633, I think it would make a lot more organizational sense to do the following:
JepExceptionfor JEP-related exceptions that occur outside of the scope of an interpreter (for example, creating aJepConfigafter creating aSharedInterpreter, or throwing to signal invalid thread access).InterpreterException, which becomes a new superclass for any exceptions thrown inside the interpreter context (either pure Python exceptions or Java exceptions which surface as a result of Python calls). I think it would be useful to attach a formatted Python traceback String to this superclass.PythonExceptiona subclass ofInterpreterException, and throw this subtype for pure Python exceptions only. It would cache the underlying Python exception as aPyObject.JavaException, a subclass ofInterpreterException, and throw this subtype for Java exceptions that occur as a result of a Python call. The underlying Java exception would be obtainable viagetCause(), as before. Although the cause was a Java exception, it may still be useful to have a Python traceback, so that's obtainable via the superclass'InterpreterException.getPythonTraceback()method.I didn't do this because it would introduce breaking changes, since
JepExceptioncurrently encompasses exceptions thrown in a Python context.Patch for memory leak when handling exceptions
This PR also fixes a memory leak in
jep_exceptions.c. When looping over the Python traceback to build JavaStackTraceElements,PySequence_GetItemcreates a new reference, but these new references are immediately passed to other functions, without decrefing:In addition,
pyLineis never decrefed. This leaks up to 4 Python objects per stack frame.This is fixed by saving all four references as named variables, and explicitly calling decref on them when apporpriate.
Commit 6ea9217 fixes this memory leak.