Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception chaining/cause not mirrored into Python #1047

Open
pelson opened this issue Apr 12, 2022 · 2 comments · May be fixed by #1057
Open

Exception chaining/cause not mirrored into Python #1047

pelson opened this issue Apr 12, 2022 · 2 comments · May be fixed by #1057
Labels
enhancement Improvement in capability planned for future release

Comments

@pelson
Copy link
Contributor

pelson commented Apr 12, 2022

Both Python and Java have exception chaining (Throwable.getCause() in Java, exception.__cause__ in Python).

This information doesn't make it across from Java to Python though. Take the following snippet:

import jpype as jp
jp.startJVM()


java = jp.JPackage('java')
e1 = java.lang.RuntimeException("Some parent exception")
e2 = java.lang.RuntimeException("Some message", e1)

When we inspect the object:

>>> print(e2.__cause__, e2.__context__)
None None

>>> raise e2
Traceback (most recent call last):
  File "/home/pelson/github/jpype/jpype/support/exception_cause.py", line 19, in <module>
    raise e2
java.lang.RuntimeException: java.lang.RuntimeException: Some message

By adding the exception to __cause__ (via e2.__cause__ = e1) we get an improved exception:

java.lang.RuntimeException: java.lang.RuntimeException: Some parent exception

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/pelson/github/jpype/jpype/support/exception_cause.py", line 19, in <module>
    raise e2
java.lang.RuntimeException: java.lang.RuntimeException: Some message

In investigating this, I also wondered if I could pass a Python exception as a cause for a Throwable, but unfortunately that wasn't possible:

>>> e1 = ValueError('foo')
>>> e2 = java.lang.RuntimeException("Some message", e1)

TypeError: No matching overloads found for constructor java.lang.RuntimeException(str,ValueError), options are:
	public java.lang.RuntimeException()
	public java.lang.RuntimeException(java.lang.String)
	public java.lang.RuntimeException(java.lang.String,java.lang.Throwable)
	public java.lang.RuntimeException(java.lang.Throwable)

I'm not sure on the practicalities, but it would be also interesting if we could make BaseException be understood as a Throwable.

@Thrameos
Copy link
Contributor

The Python exception system is mostly hardcoded which makes extensions of it very complicated. You can see the level of hacking that was required to get the stack frames to connect to each other. I have some outstanding tickets with the Python developers regarding providing methods to connect the two together but as I can't contribute to Python (seeing as my employer refuses to sign the contributor agreement) I remain stuck.

As for __cause__ that may be possible, if that field is not part of the hard coding. My main concern is that I use the __cause__ field when chaining on from Python to Java. But I would need to look more closely to see if the Java wrapper can override that field. The unfortunate side effect of the way that Python has implemented this, is that even if I were to make __cause__ to be the correct value when probed, the Python exception printer ignores user side code and simply goes to the hard coded slot. Thus to make it actually work properly with the printers I would have to convert the __cause__ object to Python (and its __cause__ etc, proactively whenever an Exception wrapper was created. This means a speed hit for a bunch of information which may get thrown away without ever being looked at. Of course if you are Java oriented and "Exceptions should be exceptional" rather than Pythonic "Exceptions are just another return path", then the speed hit should be rare.

As for making a ValueError appear as a BaseException. This is possible with the reverse bridge. The only issue it that we still having not recruited enough people to serve as beta testers/developers to complete the epypj code. I know that writing 1000 test routines and solving the pile of edge cases are not fun tasks, but it would be required to release that code.

@Thrameos Thrameos added the enhancement Improvement in capability planned for future release label Apr 23, 2022
@Thrameos
Copy link
Contributor

I looked into this some further. It is possible for me to wire up the __cause__, however this creates a conflict in the stack traces produced by Java and the ones produced within Python. The issue is that CPython does not currently let use add the Java stackframes into the exception stack, so we are currently creating a fake exception with the Java stackframes and placing it as the cause. For this to be a meaningful PR, we would need to find someway to insert a marker into the Python traceback system such that when an exception is thrown, we would know where in the Python stack trace listing the Java call occurred, and then replace the marker with the Java stackframes that we are getting from the Java exception.

I see that that whole system CFrame system is being reworked from 3.11 so it is very unclear if there is any way this can be accomplished in a cross version fashion. At least as far as I can tell, there is no public API for creating tracebacks (so we are hacking that in place), and there is no way in the public API to push a marker into the Python traceback and pop it after the Java call returns such that if an exception took place in Java that we can splice out exception information in. However, perhaps @markshannon be so kind as pointing me towards the API if it exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improvement in capability planned for future release
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants