-
Notifications
You must be signed in to change notification settings - Fork 181
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
fix: Python 3.12 support; remove usage of wstr and use updated PyLong ob_digit location #1148
fix: Python 3.12 support; remove usage of wstr and use updated PyLong ob_digit location #1148
Conversation
Fixes #1147 when it actually works, but this make the code compilable at least. |
Please add the Python 3.12 continuous integration tests from #1153 so we can monitor progress. |
- wstr was removed in CPython 3.12: https://www.python.org/dev/peps/pep-0623 - ob_digit was moved from PyObject to PyLongObject, which is accessible from long_value on PyObject
Apparently it can be NULL now; might need to be replaced with PyType_GetDict: python/cpython@a840806
92b285a
to
2ac07e5
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #1148 +/- ##
==========================================
- Coverage 87.84% 87.82% -0.02%
==========================================
Files 112 112
Lines 10276 10276
Branches 4032 4032
==========================================
- Hits 9027 9025 -2
- Misses 698 699 +1
- Partials 551 552 +1 ☔ View full report in Codecov by Sentry. |
…nt in Python 3.12
There are quite a few non-trivial changes to the internals of CPython 3.12 that JPype uses, which unfortunately makes supporting CPython 3.12 far from trivial. Currently trying to fix the segmentation fault on |
I am trying to cycle back to it after not having a computer for last 3 months.
|
What the heck. That's a long time I have to say. Did you take some sabbath? 👍 |
…e the whole test run
@Christopher-Chianelli I advice you to use pytest-xdist and invoke pytest -n2 to enable all tests to run, even if there are crashes. I already pushed this to CI. Here is an overview of all failed tests: https://dev.azure.com/jpype-project/jpype/_build/results?buildId=1129&view=ms.vss-test-web.build-test-results-tab |
@marscher Sadly my work laptop that held everything that I work with JPype on took a dive mid July. I ordered one straight out of my work catalog using funding set to expire on Oct 1 figuring that I would be up and ready to go in August. But as it was a "second computer" requiring an extra round of approvals and the vendor lowered the item price which triggered another round of signatures, then the funding expired so I had to restart the process again, and when it finally did show up the computer support needed two weeks to get it ready for use, then it was discovered the vendor had substituted the hardware for something not approved so no use at work. (Then of course with new fiscal year my available time was at zilch until December.) NEVER underestimate the mind boggling time it can take the US government can take to order a laptop. So here is the current state of Python3.12.
I have puzzled out the first. The second compiles but needs lots of testing. The third is the nastiest as until I manage to get it exactly right I will get get random memory corruption problems (which makes locating the source of the error very painfully slow.) |
@encukou Bad news.... the API for extension of objects added in 3.12 appears to be a significant miss in terms of what is required for JPype. There are two reasons for this.
Unfortunately this places JPype in a seriously bad spot for 3.12. The existing "tp_alloc" hack is currently breaking because something wrote to the space after the allocation. And the API intended to be the replacement is short of what is required for universal multiple inheritance. Is the GC now using the memory after the object or is there a bug someplace that could write into that location? |
Hmmm. The plot thickens. I search through and found that the Python garbage collection is not touching the memory after the allocation of the object. So whatever is messing with our memory reserve is not part of the GC system. That means that either a class in Python proper is corruptly writing into the area outsize of the expected space or something in one of classes (perhaps the attempt to replicate PyLong). Python always has a set of extra bytes at the end due to "itemsize+1" code, so something may be pushing into that left over space in an undetected fashion. I just need to find out which class touched it and build a workaround and we are back up an running. |
Okay I believe I have tracked this one to its source. Nothing was writing to my reserve space. Instead something clobbered all methods needs to locate the where the end of the structure is. The issue is that PyLong is acting like a VAR_Object but it no longer functions as one. Thus it allocates a var object with size 1 then resets the size field to a bit flag preventing us from figuring out how much memory it takes which is the only way to know how much memory we need to get to our reserve. This seems like a horrible abuse for the API to create something with a VAR type and then clobber the ob_size field to save some bytes which makes locating the the original number of bytes that were used in the allocation impossible. I will have to come up with a kludge to deal with this sort of abusive behavior by fixing the size of the object to something that I can get to. |
After many hours (okay not that many, but it felt like forever) I have found a kludge around PyLong. It is nasty because ob_size has its meaning change during operation from ob_size to lv_tags with different meanings. The user data replacement API appears to be a dud because it doesn't provide multiple inheritance of dissimilar bases. Unfortunately that still leaves 3 errors in the tests.
@encukou I looked deeper on the type expansion. I don't think that it would be hard to make the API work. The trick would be to instead of adding to the tp_basesize on a negative request, to simply tally the number of "end" bytes that the user is requesting and give a "user_offset" much like dict_offset to place the item safely in memory structure where it can be retrieved. Then add the memory reserve to the allocation like JPype does. The same API calls could retrieve it directly. Though if you wanted to be more general, the better solution would be to allow for multiple negative basesize values and have the type contain an offset so that it can check the table based on the multiple inheritance. Though that again gets into the prospect of conflicting baseclasses one inherits from multiple classes using negative offsets. So still a very hard problem. |
Right, a var object is free to use The new API in 3.12 deals with types like PyType, but not types like PyLong. Looks like the solution (as with many of these issues) will be to implement Mark's Grand Unified Python Object Layout, which has true multiple inheritance, clarity in who owns what, and optimization possibilities. |
@encukou I think the ob_size is mostly a red herring. The issue is that there is still not a way to request size for a memory reserve that doesn't cause classes that inherit from it to interfere. (or even know that there is memory associated with the base class). The only reason that I pay any attention to ob_size field is that there isn't a reserve memory for baseclass and retrieve memory from baseclass method which doesn't change the memory layout. I looked over the grand layout plan and it seems like a mostly breaking lift, though perhaps I am misreading. I will give it a large read when I have had more sleep. I think there is a very simple implementation for multiple inheritance base on what you included. If a class wants to add opaque memory then it requests negative base size. The negative sizes go into the reserve request which will become an address offset like dict. The offset for that memory based on the mro position. There is a slot The result is it get the opaque data from a data structure would call The only difficulty with this implementation is that you end use with a table of offsets the size of the mro in classes that use the opaque type. OTOH It could also save a huge amount of memory in the type system as a type could inherit a new slot set (Type vs ArithmeticType vs MapType) so that every slot does not need to add to the heap memory profile. You just call the cast on the type object for the slot interface that is being requested and it gives the slot table. Of course then you have burden that types become mixins, which may not be the direction of Python. And you have to decide if mixings are virtual or not (C++ hell). The system I describe only supports pure virtual mixins. |
I just realized I dont understand something. If ob_size is freely allowed to be reused, the how does negative dictoffset work? I modelled our reserve memory off dictoffset so if my implementation busts then wouldnt dictoffset also be busted on PyLong? Need to investigate. |
@encukou Eeeek! I just found the same bug that was haunting me in the Python 3.12 code. Assuming I am reading this right if you create a class derived from int (ie
Thus the dictionary position will end up way beyond the end of the object into someone else's memory. |
I have a working copy of JPype for Python 3.12. Next up it to work again with Python 3.10. |
Well, PEPs welcome... But it does seem like a real implementation would be as complex as the grand layout plan (and possibly quite similar to it, in the end).
Thanks for digging into that one! I guess it's another case of Doing this with I filed python/cpython#112743 |
Regarding the PR how limited in scope do you want?
Which is your preference? I have finally gotten permission to sign the form. |
Closing since #1158 appears to fix the issue |
@encukou Can you give me some insight as to which PR would be most acceptable in terms of API breakage and rearrangement? To fact that JPype currently requires access to Python private methods as opposed to using public API is currently the largest maintenance issue here, especially if my job decides that we don't have enough funding to support JPype (which it often does). I would like to get a few PR in that improves this situation for the long term. |
Sorry, I now realize “PEPs welcome” looks too much like “PRs welcome” :( I think this'll need a PEP -- a change proposal listing the pros and cons of possible solutions. There are too many details and people involved. And while your proposals look simple, but I don't think they'll be that simple in practice.
|
Okay so I should flesh out the solution that allows for more than one (true multiple inheritance). This could be a reasonable step toward the grand unified layout as fields and the rest can be added after the multicast. The basic idea will be negative I will save detailed discussion until I have a better idea of what is required to pull it off. Unless I create a trial PR it won't be clear where all the cogs need to fit. |
Investigating a segmentation fault with PyJP_GetAttrDescriptor